docs: archive entra materials and simplify deployment docs
This commit is contained in:
@@ -1,34 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: azure-config
|
||||
namespace: the-order
|
||||
data:
|
||||
# Azure Configuration (from .env file)
|
||||
AZURE_REGION: "westeurope" # Default, override via External Secrets
|
||||
AZURE_SUBSCRIPTION_ID: "" # Set via External Secrets Operator from Key Vault
|
||||
AZURE_TENANT_ID: "" # Set via External Secrets Operator from Key Vault
|
||||
|
||||
# Storage Configuration
|
||||
AZURE_STORAGE_ACCOUNT: "" # Set via External Secrets Operator
|
||||
AZURE_STORAGE_CONTAINER: "images"
|
||||
AZURE_STORAGE_KEY: "" # Set via External Secrets Operator
|
||||
|
||||
# Key Vault Configuration
|
||||
AZURE_KEY_VAULT_NAME: "" # Set via External Secrets Operator
|
||||
AZURE_KEY_VAULT_URI: "" # Set via External Secrets Operator
|
||||
|
||||
# CDN Configuration
|
||||
AZURE_CDN_PROFILE: "theorder-cdn"
|
||||
AZURE_CDN_ENDPOINT: "theorder-cdn-endpoint"
|
||||
CDN_BASE_URL: "" # Set via External Secrets Operator
|
||||
|
||||
# AKS Configuration
|
||||
AKS_CLUSTER_NAME: "the-order-aks"
|
||||
AKS_RESOURCE_GROUP: "the-order-rg"
|
||||
|
||||
# Database Configuration
|
||||
DATABASE_HOST: "" # Set via External Secrets Operator
|
||||
DATABASE_NAME: "theorder"
|
||||
DATABASE_USER: "theorder_admin"
|
||||
# DATABASE_PASSWORD set via External Secrets Operator
|
||||
@@ -1,58 +0,0 @@
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: SecretStore
|
||||
metadata:
|
||||
name: azure-keyvault
|
||||
namespace: the-order
|
||||
spec:
|
||||
provider:
|
||||
azurekv:
|
||||
tenantId: "${AZURE_TENANT_ID}" # Set via environment variable
|
||||
vaultUrl: "${AZURE_KEY_VAULT_URI}" # Set via environment variable
|
||||
authType: WorkloadIdentity
|
||||
serviceAccountRef:
|
||||
name: external-secrets-sa
|
||||
---
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: azure-secrets
|
||||
namespace: the-order
|
||||
spec:
|
||||
refreshInterval: 1h
|
||||
secretStoreRef:
|
||||
name: azure-keyvault
|
||||
kind: SecretStore
|
||||
target:
|
||||
name: the-order-secrets
|
||||
creationPolicy: Owner
|
||||
data:
|
||||
# Database
|
||||
- secretKey: database-url
|
||||
remoteRef:
|
||||
key: database-url
|
||||
# Azure Storage
|
||||
- secretKey: storage-account
|
||||
remoteRef:
|
||||
key: storage-account
|
||||
- secretKey: storage-key
|
||||
remoteRef:
|
||||
key: storage-key
|
||||
# Entra VerifiedID
|
||||
- secretKey: entra-tenant-id
|
||||
remoteRef:
|
||||
key: entra-tenant-id
|
||||
- secretKey: entra-client-id
|
||||
remoteRef:
|
||||
key: entra-client-id
|
||||
- secretKey: entra-client-secret
|
||||
remoteRef:
|
||||
key: entra-client-secret
|
||||
# Payment Gateway
|
||||
- secretKey: payment-gateway-api-key
|
||||
remoteRef:
|
||||
key: payment-gateway-api-key
|
||||
# Grafana
|
||||
- secretKey: grafana-admin-password
|
||||
remoteRef:
|
||||
key: grafana-admin-password
|
||||
|
||||
@@ -36,21 +36,6 @@ spec:
|
||||
secretKeyRef:
|
||||
name: the-order-secrets
|
||||
key: database-url
|
||||
- name: ENTRA_TENANT_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: the-order-secrets
|
||||
key: entra-tenant-id
|
||||
- name: ENTRA_CLIENT_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: the-order-secrets
|
||||
key: entra-client-id
|
||||
- name: ENTRA_CLIENT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: the-order-secrets
|
||||
key: entra-client-secret
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
@@ -126,4 +111,3 @@ spec:
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 80
|
||||
|
||||
|
||||
@@ -71,16 +71,3 @@ data:
|
||||
annotations:
|
||||
summary: "Database connection pool nearly exhausted"
|
||||
description: "{{ $value }}% of connections in use"
|
||||
|
||||
- name: azure
|
||||
interval: 30s
|
||||
rules:
|
||||
- alert: EntraAPIRateLimit
|
||||
expr: rate(entra_api_requests_total{status="429"}[5m]) > 0
|
||||
for: 1m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Entra API rate limit hit"
|
||||
description: "Rate limit errors detected for Entra VerifiedID API"
|
||||
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
# Identity Service Deployment with Entra VerifiedID configuration
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: identity-service
|
||||
namespace: the-order-prod
|
||||
labels:
|
||||
app: identity-service
|
||||
component: identity
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: identity-service
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: identity-service
|
||||
component: identity
|
||||
spec:
|
||||
containers:
|
||||
- name: identity-service
|
||||
image: ghcr.io/the-order/identity-service:latest
|
||||
ports:
|
||||
- containerPort: 4002
|
||||
name: http
|
||||
env:
|
||||
- name: PORT
|
||||
value: "4002"
|
||||
- name: NODE_ENV
|
||||
value: "production"
|
||||
# Entra VerifiedID Configuration
|
||||
- name: ENTRA_TENANT_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: entra-verifiedid-secrets
|
||||
key: ENTRA_TENANT_ID
|
||||
- name: ENTRA_CLIENT_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: entra-verifiedid-secrets
|
||||
key: ENTRA_CLIENT_ID
|
||||
- name: ENTRA_CLIENT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: entra-verifiedid-secrets
|
||||
key: ENTRA_CLIENT_SECRET
|
||||
- name: ENTRA_CREDENTIAL_MANIFEST_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: entra-verifiedid-secrets
|
||||
key: ENTRA_CREDENTIAL_MANIFEST_ID
|
||||
- name: ENTRA_MANIFESTS
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: entra-verifiedid-secrets
|
||||
key: ENTRA_MANIFESTS
|
||||
optional: true
|
||||
- name: ENTRA_RATE_LIMIT_ISSUANCE
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: entra-verifiedid-secrets
|
||||
key: ENTRA_RATE_LIMIT_ISSUANCE
|
||||
optional: true
|
||||
- name: ENTRA_RATE_LIMIT_VERIFICATION
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: entra-verifiedid-secrets
|
||||
key: ENTRA_RATE_LIMIT_VERIFICATION
|
||||
optional: true
|
||||
- name: ENTRA_RATE_LIMIT_STATUS_CHECK
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: entra-verifiedid-secrets
|
||||
key: ENTRA_RATE_LIMIT_STATUS_CHECK
|
||||
optional: true
|
||||
- name: ENTRA_RATE_LIMIT_GLOBAL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: entra-verifiedid-secrets
|
||||
key: ENTRA_RATE_LIMIT_GLOBAL
|
||||
optional: true
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 4002
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 4002
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /app/config
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: identity-service-config
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: identity-service
|
||||
namespace: the-order-prod
|
||||
spec:
|
||||
selector:
|
||||
app: identity-service
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 4002
|
||||
protocol: TCP
|
||||
type: ClusterIP
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: identity-service-ingress
|
||||
namespace: the-order-prod
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
nginx.ingress.kubernetes.io/rate-limit: "100"
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
tls:
|
||||
- hosts:
|
||||
- api.theorder.org
|
||||
secretName: identity-service-tls
|
||||
rules:
|
||||
- host: api.theorder.org
|
||||
http:
|
||||
paths:
|
||||
- path: /vc
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: identity-service
|
||||
port:
|
||||
number: 80
|
||||
- path: /eidas
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: identity-service
|
||||
port:
|
||||
number: 80
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
# Kubernetes Secret template for Entra VerifiedID
|
||||
# Use with External Secrets Operator or kubectl
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: entra-verifiedid-secrets
|
||||
namespace: the-order-prod
|
||||
type: Opaque
|
||||
stringData:
|
||||
# Microsoft Entra VerifiedID Configuration
|
||||
ENTRA_TENANT_ID: "" # Replace with actual tenant ID
|
||||
ENTRA_CLIENT_ID: "" # Replace with actual client ID
|
||||
ENTRA_CLIENT_SECRET: "" # Replace with actual client secret
|
||||
ENTRA_CREDENTIAL_MANIFEST_ID: "" # Replace with manifest ID
|
||||
|
||||
# Multi-manifest support (JSON format)
|
||||
# ENTRA_MANIFESTS: '{"default":"id1","diplomatic":"id2","judicial":"id3","financial":"id4"}'
|
||||
|
||||
# Rate Limiting (optional)
|
||||
ENTRA_RATE_LIMIT_ISSUANCE: "10"
|
||||
ENTRA_RATE_LIMIT_VERIFICATION: "20"
|
||||
ENTRA_RATE_LIMIT_STATUS_CHECK: "30"
|
||||
ENTRA_RATE_LIMIT_GLOBAL: "50"
|
||||
---
|
||||
# External Secret (if using External Secrets Operator)
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: entra-verifiedid-secrets
|
||||
namespace: the-order-prod
|
||||
spec:
|
||||
secretStoreRef:
|
||||
name: azure-keyvault
|
||||
kind: SecretStore
|
||||
target:
|
||||
name: entra-verifiedid-secrets
|
||||
creationPolicy: Owner
|
||||
data:
|
||||
- secretKey: ENTRA_TENANT_ID
|
||||
remoteRef:
|
||||
key: entra-tenant-id
|
||||
- secretKey: ENTRA_CLIENT_ID
|
||||
remoteRef:
|
||||
key: entra-client-id
|
||||
- secretKey: ENTRA_CLIENT_SECRET
|
||||
remoteRef:
|
||||
key: entra-client-secret
|
||||
- secretKey: ENTRA_CREDENTIAL_MANIFEST_ID
|
||||
remoteRef:
|
||||
key: entra-credential-manifest-id
|
||||
|
||||
@@ -82,24 +82,3 @@ groups:
|
||||
annotations:
|
||||
summary: "Slow database queries detected"
|
||||
description: "Average query time is {{ $value }} seconds"
|
||||
|
||||
- name: azure
|
||||
interval: 30s
|
||||
rules:
|
||||
- alert: EntraAPIRateLimit
|
||||
expr: rate(entra_api_requests_total{status="429"}[5m]) > 0
|
||||
for: 1m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Entra API rate limit hit"
|
||||
description: "Rate limit errors detected for Entra VerifiedID API"
|
||||
|
||||
- alert: AzureStorageErrors
|
||||
expr: rate(azure_storage_errors_total[5m]) > 0.01
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Azure Storage errors detected"
|
||||
description: "Storage error rate is {{ $value }} errors per second"
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
{
|
||||
"dashboard": {
|
||||
"title": "Entra VerifiedID Integration",
|
||||
"tags": ["entra", "verifiedid", "credentials"],
|
||||
"timezone": "browser",
|
||||
"panels": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Issuance Success Rate",
|
||||
"type": "gauge",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "rate(entra_credentials_issued_total{status=\"success\"}[5m]) / rate(entra_credentials_issued_total[5m]) * 100",
|
||||
"legendFormat": "Success Rate"
|
||||
}
|
||||
],
|
||||
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 0},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"unit": "percent",
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{"value": 0, "color": "red"},
|
||||
{"value": 90, "color": "yellow"},
|
||||
{"value": 95, "color": "green"}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "API Request Rate",
|
||||
"type": "graph",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "rate(entra_api_requests_total[5m])",
|
||||
"legendFormat": "{{operation}} - {{status}}"
|
||||
}
|
||||
],
|
||||
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 0}
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"title": "Error Rate by Operation",
|
||||
"type": "graph",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "rate(entra_api_errors_total[5m])",
|
||||
"legendFormat": "{{operation}} - {{error_type}}"
|
||||
}
|
||||
],
|
||||
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 8}
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"title": "Issuance Duration",
|
||||
"type": "graph",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "histogram_quantile(0.95, entra_issuance_duration_seconds_bucket)",
|
||||
"legendFormat": "p95"
|
||||
},
|
||||
{
|
||||
"expr": "histogram_quantile(0.50, entra_issuance_duration_seconds_bucket)",
|
||||
"legendFormat": "p50"
|
||||
}
|
||||
],
|
||||
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 8}
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"title": "Webhook Events",
|
||||
"type": "graph",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "rate(entra_webhooks_received_total[5m])",
|
||||
"legendFormat": "{{event_type}} - {{status}}"
|
||||
}
|
||||
],
|
||||
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 16}
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"title": "Active Requests",
|
||||
"type": "gauge",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "entra_active_requests",
|
||||
"legendFormat": "{{operation}}"
|
||||
}
|
||||
],
|
||||
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 16}
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"title": "Credentials Issued by Manifest",
|
||||
"type": "graph",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "rate(entra_credentials_issued_total[5m])",
|
||||
"legendFormat": "{{manifest_name}} - {{status}}"
|
||||
}
|
||||
],
|
||||
"gridPos": {"h": 8, "w": 24, "x": 0, "y": 24}
|
||||
}
|
||||
],
|
||||
"refresh": "30s",
|
||||
"schemaVersion": 27,
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
# Prometheus configuration for Entra VerifiedID metrics
|
||||
# Add this to your Prometheus configuration
|
||||
|
||||
scrape_configs:
|
||||
- job_name: 'identity-service-entra'
|
||||
scrape_interval: 15s
|
||||
scrape_timeout: 10s
|
||||
metrics_path: '/metrics'
|
||||
static_configs:
|
||||
- targets:
|
||||
- 'identity-service:4002'
|
||||
labels:
|
||||
service: 'identity-service'
|
||||
component: 'entra-verifiedid'
|
||||
environment: 'production'
|
||||
|
||||
# Alert rules for Entra VerifiedID
|
||||
groups:
|
||||
- name: entra_verifiedid
|
||||
interval: 30s
|
||||
rules:
|
||||
# High error rate
|
||||
- alert: EntraIssuanceErrorRateHigh
|
||||
expr: |
|
||||
rate(entra_api_errors_total[5m]) / rate(entra_api_requests_total[5m]) > 0.10
|
||||
for: 5m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Entra VerifiedID error rate is high"
|
||||
description: "Error rate is {{ $value | humanizePercentage }} (threshold: 10%)"
|
||||
|
||||
# High latency
|
||||
- alert: EntraIssuanceLatencyHigh
|
||||
expr: |
|
||||
histogram_quantile(0.95, entra_api_request_duration_seconds_bucket{operation="issueCredential"}) > 10
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Entra VerifiedID latency is high"
|
||||
description: "p95 latency is {{ $value }}s (threshold: 10s)"
|
||||
|
||||
# Webhook processing failures
|
||||
- alert: EntraWebhookProcessingFailed
|
||||
expr: |
|
||||
rate(entra_webhook_errors_total[5m]) > 0
|
||||
for: 2m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Entra webhook processing failures detected"
|
||||
description: "{{ $value }} webhook errors in the last 5 minutes"
|
||||
|
||||
# API down
|
||||
- alert: EntraAPIDown
|
||||
expr: |
|
||||
rate(entra_api_requests_total{status="success"}[5m]) == 0
|
||||
for: 5m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Entra VerifiedID API appears to be down"
|
||||
description: "No successful API requests in the last 5 minutes"
|
||||
|
||||
# Rate limit approaching
|
||||
- alert: EntraRateLimitApproaching
|
||||
expr: |
|
||||
rate(entra_api_requests_total[1m]) > 40
|
||||
for: 2m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Entra API rate limit approaching"
|
||||
description: "Request rate is {{ $value }}/min (limit: 50/min)"
|
||||
|
||||
+8
-125
@@ -1,130 +1,13 @@
|
||||
# Azure Setup Scripts
|
||||
# Infrastructure Scripts
|
||||
|
||||
This directory contains scripts for setting up Azure infrastructure prerequisites for The Order.
|
||||
This directory contains supporting infrastructure scripts for the active Sankofa Phoenix / Proxmox deployment model.
|
||||
|
||||
## Scripts
|
||||
Use these active entry points:
|
||||
|
||||
### 1. `azure-setup.sh` - Complete Azure Setup
|
||||
- `scripts/deploy/deploy.sh`
|
||||
- `docs/deployment/overview.md`
|
||||
- `docs/deployment/DEPLOYMENT_QUICK_REFERENCE.md`
|
||||
|
||||
Comprehensive setup script that:
|
||||
- Lists all available Azure Commercial regions (excluding US)
|
||||
- Sets default region to West Europe
|
||||
- Checks and registers required resource providers
|
||||
- Checks quotas for primary regions
|
||||
- Generates reports
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
./infra/scripts/azure-setup.sh
|
||||
```
|
||||
|
||||
**Output Files:**
|
||||
- `azure-regions.txt` - List of all non-US regions
|
||||
- `azure-quotas.txt` - Quota information for primary regions
|
||||
|
||||
### 2. `azure-register-providers.sh` - Register Resource Providers
|
||||
|
||||
Registers all required Azure Resource Providers for The Order.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
./infra/scripts/azure-register-providers.sh
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
- Checks registration status of all required providers
|
||||
- Registers unregistered providers
|
||||
- Waits for registration to complete
|
||||
- Reports final status
|
||||
|
||||
### 3. `azure-check-quotas.sh` - Check Quotas for All Regions
|
||||
|
||||
Checks quotas for all non-US Azure regions.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
./infra/scripts/azure-check-quotas.sh
|
||||
```
|
||||
|
||||
**Output:**
|
||||
- `azure-quotas-all-regions.txt` - Detailed quota information for all regions
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **Azure CLI installed**
|
||||
```bash
|
||||
# Check if installed
|
||||
az --version
|
||||
|
||||
# Install if needed
|
||||
# https://docs.microsoft.com/en-us/cli/azure/install-azure-cli
|
||||
```
|
||||
|
||||
2. **Azure CLI logged in**
|
||||
```bash
|
||||
az login
|
||||
az account show
|
||||
```
|
||||
|
||||
3. **Required permissions**
|
||||
- Subscription Contributor or Owner role
|
||||
- Ability to register resource providers
|
||||
- Ability to check quotas
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. **Login to Azure**
|
||||
```bash
|
||||
az login
|
||||
```
|
||||
|
||||
2. **Run complete setup**
|
||||
```bash
|
||||
./infra/scripts/azure-setup.sh
|
||||
```
|
||||
|
||||
3. **Verify providers are registered**
|
||||
```bash
|
||||
./infra/scripts/azure-register-providers.sh
|
||||
```
|
||||
|
||||
4. **Check quotas**
|
||||
```bash
|
||||
./infra/scripts/azure-check-quotas.sh
|
||||
```
|
||||
|
||||
## Required Resource Providers
|
||||
|
||||
See `infra/terraform/AZURE_RESOURCE_PROVIDERS.md` for complete list.
|
||||
|
||||
## Default Region
|
||||
|
||||
**West Europe (westeurope)** is the default region. US Commercial and Government regions are **not used**.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Script fails with "not logged in"
|
||||
```bash
|
||||
az login
|
||||
az account set --subscription <subscription-id>
|
||||
```
|
||||
|
||||
### Provider registration fails
|
||||
- Check subscription permissions
|
||||
- Verify subscription is active
|
||||
- Wait 5-10 minutes and retry
|
||||
|
||||
### Quota check fails
|
||||
- Some regions may not support all quota types
|
||||
- Check individual regions manually if needed
|
||||
|
||||
## Output Files
|
||||
|
||||
All scripts generate output files in the current directory:
|
||||
|
||||
- `azure-regions.txt` - List of available regions
|
||||
- `azure-quotas.txt` - Quotas for primary regions
|
||||
- `azure-quotas-all-regions.txt` - Quotas for all regions
|
||||
|
||||
Review these files to understand available resources and limits.
|
||||
Historical provider setup scripts have been quarantined under:
|
||||
|
||||
- `archive/quarantined-legacy-stack/infra/scripts/`
|
||||
|
||||
@@ -1,314 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Azure CDN Setup for Credential Seal Images
|
||||
# Creates storage account, container, and CDN profile/endpoint
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[✓]${NC} $1"; }
|
||||
log_warning() { echo -e "${YELLOW}[!]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[✗]${NC} $1"; }
|
||||
|
||||
cd "$(dirname "$0")/../.."
|
||||
|
||||
# Check if Azure CLI is installed
|
||||
if ! command -v az &> /dev/null; then
|
||||
log_error "Azure CLI is not installed"
|
||||
echo "Install from: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if logged in
|
||||
if ! az account show &> /dev/null; then
|
||||
log_warning "Not logged in to Azure. Please log in:"
|
||||
echo " az login"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get subscription info
|
||||
SUBSCRIPTION_ID=$(az account show --query id -o tsv)
|
||||
SUBSCRIPTION_NAME=$(az account show --query name -o tsv)
|
||||
TENANT_ID=$(az account show --query tenantId -o tsv)
|
||||
|
||||
log_info "Azure Subscription: ${SUBSCRIPTION_NAME} (${SUBSCRIPTION_ID})"
|
||||
echo ""
|
||||
|
||||
# Configuration
|
||||
RESOURCE_GROUP="${AZURE_RESOURCE_GROUP:-the-order-cdn-rg}"
|
||||
LOCATION="${AZURE_LOCATION:-westeurope}"
|
||||
STORAGE_ACCOUNT_NAME="${AZURE_STORAGE_ACCOUNT:-theordercdn$(date +%s | tail -c 6)}"
|
||||
CONTAINER_NAME="images"
|
||||
CDN_PROFILE_NAME="${AZURE_CDN_PROFILE:-theorder-cdn-profile}"
|
||||
CDN_ENDPOINT_NAME="${AZURE_CDN_ENDPOINT:-theorder-cdn-endpoint}"
|
||||
|
||||
# Validate storage account name (must be lowercase alphanumeric, 3-24 chars)
|
||||
STORAGE_ACCOUNT_NAME=$(echo "${STORAGE_ACCOUNT_NAME}" | tr '[:upper:]' '[:lower:]' | tr -cd 'a-z0-9' | cut -c1-24)
|
||||
if [ ${#STORAGE_ACCOUNT_NAME} -lt 3 ]; then
|
||||
STORAGE_ACCOUNT_NAME="theordercdn$(date +%s | tail -c 6)"
|
||||
fi
|
||||
|
||||
log_info "Configuration:"
|
||||
echo " Resource Group: ${RESOURCE_GROUP}"
|
||||
echo " Location: ${LOCATION}"
|
||||
echo " Storage Account: ${STORAGE_ACCOUNT_NAME}"
|
||||
echo " Container: ${CONTAINER_NAME}"
|
||||
echo " CDN Profile: ${CDN_PROFILE_NAME}"
|
||||
echo " CDN Endpoint: ${CDN_ENDPOINT_NAME}"
|
||||
echo ""
|
||||
|
||||
# Step 1: Check quotas
|
||||
log_info "Step 1: Checking Azure quotas..."
|
||||
QUOTA_FILE="azure-cdn-quotas.txt"
|
||||
cat > "${QUOTA_FILE}" << EOF
|
||||
Azure Quota Check for CDN Setup
|
||||
Generated: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
|
||||
Subscription: ${SUBSCRIPTION_NAME} (${SUBSCRIPTION_ID})
|
||||
Location: ${LOCATION}
|
||||
|
||||
EOF
|
||||
|
||||
# Check storage account quota
|
||||
log_info " Checking storage account quota..."
|
||||
STORAGE_QUOTA=$(az storage account show-usage --location "${LOCATION}" -o json 2>/dev/null || echo "{}")
|
||||
STORAGE_CURRENT=$(echo "${STORAGE_QUOTA}" | jq -r '.currentValue // 0' 2>/dev/null || echo "0")
|
||||
STORAGE_LIMIT=$(echo "${STORAGE_QUOTA}" | jq -r '.limit // 250' 2>/dev/null || echo "250")
|
||||
|
||||
echo "Storage Accounts:" >> "${QUOTA_FILE}"
|
||||
echo " Current: ${STORAGE_CURRENT}" >> "${QUOTA_FILE}"
|
||||
echo " Limit: ${STORAGE_LIMIT}" >> "${QUOTA_FILE}"
|
||||
|
||||
if [ "${STORAGE_CURRENT}" -ge "${STORAGE_LIMIT}" ]; then
|
||||
log_error "Storage account quota exceeded (${STORAGE_CURRENT}/${STORAGE_LIMIT})"
|
||||
log_info "Request quota increase: https://portal.azure.com/#blade/Microsoft_Azure_Support/HelpAndSupportBlade"
|
||||
exit 1
|
||||
else
|
||||
log_success "Storage account quota OK (${STORAGE_CURRENT}/${STORAGE_LIMIT})"
|
||||
fi
|
||||
|
||||
# Check CDN profile quota
|
||||
log_info " Checking CDN profile quota..."
|
||||
CDN_QUOTA=$(az cdn profile list --query "[].{Name:name}" -o tsv 2>/dev/null | wc -l || echo "0")
|
||||
CDN_LIMIT=25 # Default Azure CDN limit
|
||||
|
||||
echo "CDN Profiles:" >> "${QUOTA_FILE}"
|
||||
echo " Current: ${CDN_QUOTA}" >> "${QUOTA_FILE}"
|
||||
echo " Limit: ${CDN_LIMIT}" >> "${QUOTA_FILE}"
|
||||
|
||||
if [ "${CDN_QUOTA}" -ge "${CDN_LIMIT}" ]; then
|
||||
log_warning "CDN profile quota may be exceeded (${CDN_QUOTA}/${CDN_LIMIT})"
|
||||
else
|
||||
log_success "CDN profile quota OK (${CDN_QUOTA}/${CDN_LIMIT})"
|
||||
fi
|
||||
|
||||
log_success "Quota check complete. Report: ${QUOTA_FILE}"
|
||||
echo ""
|
||||
|
||||
# Step 2: Create resource group
|
||||
log_info "Step 2: Creating resource group..."
|
||||
if az group show --name "${RESOURCE_GROUP}" &> /dev/null; then
|
||||
log_success "Resource group already exists: ${RESOURCE_GROUP}"
|
||||
else
|
||||
if az group create --name "${RESOURCE_GROUP}" --location "${LOCATION}" -o json &> /dev/null; then
|
||||
log_success "Resource group created: ${RESOURCE_GROUP}"
|
||||
else
|
||||
log_error "Failed to create resource group"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Step 3: Create storage account
|
||||
log_info "Step 3: Creating storage account..."
|
||||
if az storage account show --name "${STORAGE_ACCOUNT_NAME}" --resource-group "${RESOURCE_GROUP}" &> /dev/null; then
|
||||
log_success "Storage account already exists: ${STORAGE_ACCOUNT_NAME}"
|
||||
else
|
||||
log_info " Creating storage account (this may take a few minutes)..."
|
||||
if az storage account create \
|
||||
--name "${STORAGE_ACCOUNT_NAME}" \
|
||||
--resource-group "${RESOURCE_GROUP}" \
|
||||
--location "${LOCATION}" \
|
||||
--sku Standard_LRS \
|
||||
--kind StorageV2 \
|
||||
--min-tls-version TLS1_2 \
|
||||
--allow-blob-public-access true \
|
||||
-o json &> /dev/null; then
|
||||
log_success "Storage account created: ${STORAGE_ACCOUNT_NAME}"
|
||||
else
|
||||
log_error "Failed to create storage account"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Get storage account key
|
||||
STORAGE_KEY=$(az storage account keys list \
|
||||
--resource-group "${RESOURCE_GROUP}" \
|
||||
--account-name "${STORAGE_ACCOUNT_NAME}" \
|
||||
--query "[0].value" -o tsv)
|
||||
|
||||
echo ""
|
||||
|
||||
# Step 4: Create container
|
||||
log_info "Step 4: Creating storage container..."
|
||||
if az storage container show \
|
||||
--name "${CONTAINER_NAME}" \
|
||||
--account-name "${STORAGE_ACCOUNT_NAME}" \
|
||||
--account-key "${STORAGE_KEY}" \
|
||||
&> /dev/null; then
|
||||
log_success "Container already exists: ${CONTAINER_NAME}"
|
||||
else
|
||||
if az storage container create \
|
||||
--name "${CONTAINER_NAME}" \
|
||||
--account-name "${STORAGE_ACCOUNT_NAME}" \
|
||||
--account-key "${STORAGE_KEY}" \
|
||||
--public-access blob \
|
||||
-o json &> /dev/null; then
|
||||
log_success "Container created: ${CONTAINER_NAME} (public blob access)"
|
||||
else
|
||||
log_error "Failed to create container"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Step 5: Create CDN profile
|
||||
log_info "Step 5: Creating CDN profile..."
|
||||
if az cdn profile show \
|
||||
--name "${CDN_PROFILE_NAME}" \
|
||||
--resource-group "${RESOURCE_GROUP}" \
|
||||
&> /dev/null; then
|
||||
log_success "CDN profile already exists: ${CDN_PROFILE_NAME}"
|
||||
else
|
||||
log_info " Creating CDN profile (this may take a few minutes)..."
|
||||
if az cdn profile create \
|
||||
--name "${CDN_PROFILE_NAME}" \
|
||||
--resource-group "${RESOURCE_GROUP}" \
|
||||
--sku Standard_Microsoft \
|
||||
-o json &> /dev/null; then
|
||||
log_success "CDN profile created: ${CDN_PROFILE_NAME}"
|
||||
else
|
||||
log_warning "Failed to create CDN profile (may need manual creation)"
|
||||
log_info "Create manually: https://portal.azure.com"
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Step 6: Create CDN endpoint
|
||||
log_info "Step 6: Creating CDN endpoint..."
|
||||
if az cdn endpoint show \
|
||||
--name "${CDN_ENDPOINT_NAME}" \
|
||||
--profile-name "${CDN_PROFILE_NAME}" \
|
||||
--resource-group "${RESOURCE_GROUP}" \
|
||||
&> /dev/null; then
|
||||
log_success "CDN endpoint already exists: ${CDN_ENDPOINT_NAME}"
|
||||
CDN_ENDPOINT_URL=$(az cdn endpoint show \
|
||||
--name "${CDN_ENDPOINT_NAME}" \
|
||||
--profile-name "${CDN_PROFILE_NAME}" \
|
||||
--resource-group "${RESOURCE_GROUP}" \
|
||||
--query "hostName" -o tsv)
|
||||
else
|
||||
ORIGIN_HOST="${STORAGE_ACCOUNT_NAME}.blob.core.windows.net"
|
||||
log_info " Creating CDN endpoint (this may take 10-15 minutes)..."
|
||||
if az cdn endpoint create \
|
||||
--name "${CDN_ENDPOINT_NAME}" \
|
||||
--profile-name "${CDN_PROFILE_NAME}" \
|
||||
--resource-group "${RESOURCE_GROUP}" \
|
||||
--origin "${ORIGIN_HOST}" \
|
||||
--origin-host-header "${ORIGIN_HOST}" \
|
||||
--enable-compression true \
|
||||
-o json &> /dev/null; then
|
||||
log_success "CDN endpoint created: ${CDN_ENDPOINT_NAME}"
|
||||
# Wait for endpoint to be ready
|
||||
log_info " Waiting for CDN endpoint to be ready..."
|
||||
sleep 30
|
||||
CDN_ENDPOINT_URL=$(az cdn endpoint show \
|
||||
--name "${CDN_ENDPOINT_NAME}" \
|
||||
--profile-name "${CDN_PROFILE_NAME}" \
|
||||
--resource-group "${RESOURCE_GROUP}" \
|
||||
--query "hostName" -o tsv 2>/dev/null || echo "")
|
||||
else
|
||||
log_warning "Failed to create CDN endpoint (may need manual creation)"
|
||||
CDN_ENDPOINT_URL=""
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Step 7: Configure CORS (if needed)
|
||||
log_info "Step 7: Configuring CORS..."
|
||||
az storage cors add \
|
||||
--services b \
|
||||
--methods GET HEAD OPTIONS \
|
||||
--origins "*" \
|
||||
--allowed-headers "*" \
|
||||
--exposed-headers "*" \
|
||||
--max-age 3600 \
|
||||
--account-name "${STORAGE_ACCOUNT_NAME}" \
|
||||
--account-key "${STORAGE_KEY}" \
|
||||
&> /dev/null && log_success "CORS configured" || log_warning "CORS configuration skipped"
|
||||
echo ""
|
||||
|
||||
# Step 8: Generate configuration
|
||||
log_info "Step 8: Generating configuration..."
|
||||
CONFIG_FILE="azure-cdn-config.env"
|
||||
cat > "${CONFIG_FILE}" << EOF
|
||||
# Azure CDN Configuration
|
||||
# Generated: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
|
||||
|
||||
# Storage Account
|
||||
AZURE_STORAGE_ACCOUNT=${STORAGE_ACCOUNT_NAME}
|
||||
AZURE_STORAGE_KEY=${STORAGE_KEY}
|
||||
AZURE_STORAGE_CONTAINER=${CONTAINER_NAME}
|
||||
AZURE_RESOURCE_GROUP=${RESOURCE_GROUP}
|
||||
AZURE_LOCATION=${LOCATION}
|
||||
|
||||
# CDN
|
||||
AZURE_CDN_PROFILE=${CDN_PROFILE_NAME}
|
||||
AZURE_CDN_ENDPOINT=${CDN_ENDPOINT_NAME}
|
||||
AZURE_CDN_ENDPOINT_URL=${CDN_ENDPOINT_URL:-}
|
||||
|
||||
# CDN Base URLs
|
||||
# Direct Blob Storage URL
|
||||
CDN_BASE_URL_BLOB=https://${STORAGE_ACCOUNT_NAME}.blob.core.windows.net/${CONTAINER_NAME}/
|
||||
# CDN URL (use this once CDN endpoint is ready)
|
||||
if [ -n "${CDN_ENDPOINT_URL}" ]; then
|
||||
CDN_BASE_URL_CDN=https://${CDN_ENDPOINT_URL}/${CONTAINER_NAME}/
|
||||
else
|
||||
CDN_BASE_URL_CDN=https://${CDN_ENDPOINT_NAME}.azureedge.net/${CONTAINER_NAME}/
|
||||
fi
|
||||
|
||||
# Recommended (use CDN URL if available, otherwise blob URL)
|
||||
CDN_BASE_URL=\${CDN_BASE_URL_CDN:-${CDN_BASE_URL_BLOB}}
|
||||
EOF
|
||||
|
||||
log_success "Configuration saved: ${CONFIG_FILE}"
|
||||
echo ""
|
||||
|
||||
# Summary
|
||||
log_info "=== Setup Summary ==="
|
||||
echo ""
|
||||
log_success "Resource Group: ${RESOURCE_GROUP}"
|
||||
log_success "Storage Account: ${STORAGE_ACCOUNT_NAME}"
|
||||
log_success "Container: ${CONTAINER_NAME} (public blob access)"
|
||||
log_success "CDN Profile: ${CDN_PROFILE_NAME}"
|
||||
log_success "CDN Endpoint: ${CDN_ENDPOINT_NAME}"
|
||||
echo ""
|
||||
log_info "URLs:"
|
||||
echo " Blob Storage: https://${STORAGE_ACCOUNT_NAME}.blob.core.windows.net/${CONTAINER_NAME}/"
|
||||
if [ -n "${CDN_ENDPOINT_URL}" ]; then
|
||||
echo " CDN: https://${CDN_ENDPOINT_URL}/${CONTAINER_NAME}/"
|
||||
else
|
||||
echo " CDN: (endpoint may still be provisioning, check in Azure Portal)"
|
||||
fi
|
||||
echo ""
|
||||
log_info "Next Steps:"
|
||||
echo "1. Source configuration: source ${CONFIG_FILE}"
|
||||
echo "2. Upload PNG files: ./scripts/deploy/upload-seals-to-azure.sh"
|
||||
echo "3. Update manifest URLs: CDN_BASE_URL=\${CDN_BASE_URL_CDN} ./scripts/deploy/update-manifest-seal-urls.sh"
|
||||
echo ""
|
||||
log_success "Azure CDN setup complete!"
|
||||
|
||||
@@ -1,170 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Comprehensive Azure quota check for CDN and storage setup
|
||||
# Checks all quotas needed for credential seal CDN deployment
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${BLUE}[CHECK]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[✓]${NC} $1"; }
|
||||
log_warning() { echo -e "${YELLOW}[!]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[✗]${NC} $1"; }
|
||||
|
||||
# Check if Azure CLI is installed
|
||||
if ! command -v az &> /dev/null; then
|
||||
log_error "Azure CLI is not installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if logged in
|
||||
if ! az account show &> /dev/null; then
|
||||
log_error "Not logged in to Azure. Please log in: az login"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SUBSCRIPTION_ID=$(az account show --query id -o tsv)
|
||||
SUBSCRIPTION_NAME=$(az account show --query name -o tsv)
|
||||
LOCATION="${AZURE_LOCATION:-westeurope}"
|
||||
|
||||
log_info "Azure Quota Check for CDN Setup"
|
||||
echo "Subscription: ${SUBSCRIPTION_NAME} (${SUBSCRIPTION_ID})"
|
||||
echo "Location: ${LOCATION}"
|
||||
echo ""
|
||||
|
||||
QUOTA_FILE="azure-cdn-quota-report.txt"
|
||||
cat > "${QUOTA_FILE}" << EOF
|
||||
Azure CDN Quota Report
|
||||
Generated: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
|
||||
Subscription: ${SUBSCRIPTION_NAME} (${SUBSCRIPTION_ID})
|
||||
Location: ${LOCATION}
|
||||
|
||||
EOF
|
||||
|
||||
ISSUES=0
|
||||
WARNINGS=0
|
||||
|
||||
# 1. Storage Account Quota
|
||||
log_info "1. Storage Account Quota"
|
||||
STORAGE_QUOTA=$(az storage account show-usage --location "${LOCATION}" -o json 2>/dev/null || echo "{}")
|
||||
STORAGE_CURRENT=$(echo "${STORAGE_QUOTA}" | jq -r '.currentValue // 0' 2>/dev/null || echo "0")
|
||||
STORAGE_LIMIT=$(echo "${STORAGE_QUOTA}" | jq -r '.limit // 250' 2>/dev/null || echo "250")
|
||||
|
||||
echo "Storage Accounts:" >> "${QUOTA_FILE}"
|
||||
echo " Current: ${STORAGE_CURRENT}" >> "${QUOTA_FILE}"
|
||||
echo " Limit: ${STORAGE_LIMIT}" >> "${QUOTA_FILE}"
|
||||
echo " Available: $((STORAGE_LIMIT - STORAGE_CURRENT))" >> "${QUOTA_FILE}"
|
||||
|
||||
if [ "${STORAGE_CURRENT}" -ge "${STORAGE_LIMIT}" ]; then
|
||||
log_error " Quota exceeded: ${STORAGE_CURRENT}/${STORAGE_LIMIT}"
|
||||
((ISSUES++))
|
||||
elif [ $((STORAGE_LIMIT - STORAGE_CURRENT)) -lt 1 ]; then
|
||||
log_warning " Quota nearly full: ${STORAGE_CURRENT}/${STORAGE_LIMIT}"
|
||||
((WARNINGS++))
|
||||
else
|
||||
log_success " OK: ${STORAGE_CURRENT}/${STORAGE_LIMIT} (${STORAGE_LIMIT} - ${STORAGE_CURRENT} available)"
|
||||
fi
|
||||
|
||||
# 2. CDN Profile Quota
|
||||
log_info "2. CDN Profile Quota"
|
||||
CDN_PROFILES=$(az cdn profile list --query "[].{Name:name}" -o tsv 2>/dev/null | wc -l || echo "0")
|
||||
CDN_LIMIT=25 # Default Azure CDN limit
|
||||
|
||||
echo "" >> "${QUOTA_FILE}"
|
||||
echo "CDN Profiles:" >> "${QUOTA_FILE}"
|
||||
echo " Current: ${CDN_PROFILES}" >> "${QUOTA_FILE}"
|
||||
echo " Limit: ${CDN_LIMIT}" >> "${QUOTA_FILE}"
|
||||
echo " Available: $((CDN_LIMIT - CDN_PROFILES))" >> "${QUOTA_FILE}"
|
||||
|
||||
if [ "${CDN_PROFILES}" -ge "${CDN_LIMIT}" ]; then
|
||||
log_error " Quota exceeded: ${CDN_PROFILES}/${CDN_LIMIT}"
|
||||
((ISSUES++))
|
||||
elif [ $((CDN_LIMIT - CDN_PROFILES)) -lt 2 ]; then
|
||||
log_warning " Quota nearly full: ${CDN_PROFILES}/${CDN_LIMIT}"
|
||||
((WARNINGS++))
|
||||
else
|
||||
log_success " OK: ${CDN_PROFILES}/${CDN_LIMIT} ($((CDN_LIMIT - CDN_PROFILES)) available)"
|
||||
fi
|
||||
|
||||
# 3. Resource Group Quota
|
||||
log_info "3. Resource Group Quota"
|
||||
RG_COUNT=$(az group list --query "[].{Name:name}" -o tsv 2>/dev/null | wc -l || echo "0")
|
||||
RG_LIMIT=980 # Default Azure limit
|
||||
|
||||
echo "" >> "${QUOTA_FILE}"
|
||||
echo "Resource Groups:" >> "${QUOTA_FILE}"
|
||||
echo " Current: ${RG_COUNT}" >> "${QUOTA_FILE}"
|
||||
echo " Limit: ${RG_LIMIT}" >> "${QUOTA_FILE}"
|
||||
echo " Available: $((RG_LIMIT - RG_COUNT))" >> "${QUOTA_FILE}"
|
||||
|
||||
if [ "${RG_COUNT}" -ge "${RG_LIMIT}" ]; then
|
||||
log_error " Quota exceeded: ${RG_COUNT}/${RG_LIMIT}"
|
||||
((ISSUES++))
|
||||
else
|
||||
log_success " OK: ${RG_COUNT}/${RG_LIMIT} ($((RG_LIMIT - RG_COUNT)) available)"
|
||||
fi
|
||||
|
||||
# 4. Storage Account Capacity (if we can check)
|
||||
log_info "4. Storage Account Capacity"
|
||||
echo "" >> "${QUOTA_FILE}"
|
||||
echo "Storage Account Capacity:" >> "${QUOTA_FILE}"
|
||||
echo " Note: Capacity limits depend on subscription type" >> "${QUOTA_FILE}"
|
||||
echo " Standard accounts: Up to 5 PiB per account" >> "${QUOTA_FILE}"
|
||||
log_success " OK: Sufficient for credential images (files are small)"
|
||||
|
||||
# 5. CDN Endpoint Quota
|
||||
log_info "5. CDN Endpoint Quota"
|
||||
CDN_ENDPOINTS=$(az cdn endpoint list --query "[].{Name:name}" -o tsv 2>/dev/null | wc -l)
|
||||
CDN_ENDPOINTS=${CDN_ENDPOINTS:-0}
|
||||
CDN_ENDPOINT_LIMIT=25 # Per profile
|
||||
|
||||
echo "" >> "${QUOTA_FILE}"
|
||||
echo "CDN Endpoints:" >> "${QUOTA_FILE}"
|
||||
echo " Current: ${CDN_ENDPOINTS}" >> "${QUOTA_FILE}"
|
||||
echo " Limit: ${CDN_ENDPOINT_LIMIT} per profile" >> "${QUOTA_FILE}"
|
||||
|
||||
if [ "${CDN_ENDPOINTS}" -ge "${CDN_ENDPOINT_LIMIT}" ]; then
|
||||
log_warning " Many endpoints: ${CDN_ENDPOINTS} (limit: ${CDN_ENDPOINT_LIMIT} per profile)"
|
||||
((WARNINGS++))
|
||||
else
|
||||
log_success " OK: ${CDN_ENDPOINTS} endpoints"
|
||||
fi
|
||||
|
||||
# 6. Network Bandwidth (informational)
|
||||
log_info "6. Network Bandwidth"
|
||||
echo "" >> "${QUOTA_FILE}"
|
||||
echo "Network Bandwidth:" >> "${QUOTA_FILE}"
|
||||
echo " Note: Bandwidth limits depend on subscription and region" >> "${QUOTA_FILE}"
|
||||
echo " For credential images (small files), bandwidth should be sufficient" >> "${QUOTA_FILE}"
|
||||
log_success " OK: Credential images are small files"
|
||||
|
||||
# Summary
|
||||
echo ""
|
||||
log_info "=== Quota Summary ==="
|
||||
log_success "Checks passed: Multiple"
|
||||
if [ ${WARNINGS} -gt 0 ]; then
|
||||
log_warning "Warnings: ${WARNINGS}"
|
||||
fi
|
||||
if [ ${ISSUES} -gt 0 ]; then
|
||||
log_error "Issues: ${ISSUES}"
|
||||
echo ""
|
||||
log_info "To request quota increases:"
|
||||
echo " https://portal.azure.com/#blade/Microsoft_Azure_Support/HelpAndSupportBlade"
|
||||
echo " Or use: az vm list-usage --location ${LOCATION}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_success "Quota report saved: ${QUOTA_FILE}"
|
||||
|
||||
if [ ${ISSUES} -eq 0 ]; then
|
||||
log_success "All quotas are sufficient for CDN setup!"
|
||||
exit 0
|
||||
else
|
||||
log_error "Some quotas need attention before proceeding"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Azure Quota Check Script
|
||||
# Checks quotas for all non-US Azure regions
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
# Colors
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Check if Azure CLI is installed
|
||||
if ! command -v az &> /dev/null; then
|
||||
echo -e "${RED}Error: Azure CLI is not installed.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if logged in
|
||||
if ! az account show &> /dev/null; then
|
||||
echo -e "${YELLOW}Please log in to Azure...${NC}"
|
||||
az login
|
||||
fi
|
||||
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo -e "${BLUE}Azure Quota Check - All Non-US Regions${NC}"
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo ""
|
||||
|
||||
# Get all non-US regions
|
||||
echo -e "${YELLOW}Fetching non-US regions...${NC}"
|
||||
REGIONS=$(az account list-locations \
|
||||
--query "[?metadata.regionType=='Physical' && !contains(name, 'us')].name" \
|
||||
-o tsv)
|
||||
|
||||
REGION_COUNT=$(echo "${REGIONS}" | wc -l)
|
||||
echo -e "${GREEN}Found ${REGION_COUNT} non-US regions${NC}"
|
||||
echo ""
|
||||
|
||||
# Output file
|
||||
QUOTA_FILE="azure-quotas-all-regions.txt"
|
||||
> "${QUOTA_FILE}"
|
||||
|
||||
echo "Azure Quota Report - All Non-US Regions" >> "${QUOTA_FILE}"
|
||||
echo "Generated: $(date)" >> "${QUOTA_FILE}"
|
||||
echo "Subscription: $(az account show --query name -o tsv)" >> "${QUOTA_FILE}"
|
||||
echo "========================================" >> "${QUOTA_FILE}"
|
||||
echo "" >> "${QUOTA_FILE}"
|
||||
|
||||
# Check quotas for each region
|
||||
REGION_INDEX=0
|
||||
for region in ${REGIONS}; do
|
||||
REGION_INDEX=$((REGION_INDEX + 1))
|
||||
echo -e "${BLUE}[${REGION_INDEX}/${REGION_COUNT}] Checking ${region}...${NC}"
|
||||
|
||||
echo "" >> "${QUOTA_FILE}"
|
||||
echo "========================================" >> "${QUOTA_FILE}"
|
||||
echo "Region: ${region}" >> "${QUOTA_FILE}"
|
||||
echo "========================================" >> "${QUOTA_FILE}"
|
||||
|
||||
# VM quotas
|
||||
echo "VM Family Quotas:" >> "${QUOTA_FILE}"
|
||||
az vm list-usage --location "${region}" -o table >> "${QUOTA_FILE}" 2>/dev/null || echo " Unable to fetch VM quotas" >> "${QUOTA_FILE}"
|
||||
echo "" >> "${QUOTA_FILE}"
|
||||
|
||||
# Storage quotas
|
||||
echo "Storage Account Quota:" >> "${QUOTA_FILE}"
|
||||
az storage account show-usage --location "${region}" -o json >> "${QUOTA_FILE}" 2>/dev/null || echo " Unable to fetch storage quotas" >> "${QUOTA_FILE}"
|
||||
echo "" >> "${QUOTA_FILE}"
|
||||
|
||||
# Network quotas
|
||||
echo "Network Quotas:" >> "${QUOTA_FILE}"
|
||||
az network list-usages --location "${region}" -o table >> "${QUOTA_FILE}" 2>/dev/null || echo " Unable to fetch network quotas" >> "${QUOTA_FILE}"
|
||||
echo "" >> "${QUOTA_FILE}"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}✓ Quota check complete${NC}"
|
||||
echo -e "${GREEN}✓ Results saved to: ${QUOTA_FILE}${NC}"
|
||||
echo ""
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Complete Azure setup using .env file
|
||||
# This script orchestrates the entire Azure deployment setup process
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
echo "╔══════════════════════════════════════════════════════════════╗"
|
||||
echo "║ COMPLETE AZURE SETUP FROM .ENV FILE ║"
|
||||
echo "╚══════════════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
|
||||
# Step 1: Integrate existing CDN config if available
|
||||
echo "Step 1: Integrating existing CDN configuration..."
|
||||
"$SCRIPT_DIR/azure-integrate-cdn-env.sh"
|
||||
|
||||
# Step 2: Validate environment
|
||||
echo ""
|
||||
echo "Step 2: Validating environment variables..."
|
||||
source "$SCRIPT_DIR/azure-validate-env.sh"
|
||||
|
||||
# Step 3: Sync to Terraform
|
||||
echo ""
|
||||
echo "Step 3: Syncing environment to Terraform..."
|
||||
"$SCRIPT_DIR/azure-sync-env-to-terraform.sh"
|
||||
|
||||
# Step 4: Update Kubernetes configs
|
||||
echo ""
|
||||
echo "Step 4: Updating Kubernetes configurations..."
|
||||
"$SCRIPT_DIR/azure-update-k8s-secrets.sh"
|
||||
|
||||
echo ""
|
||||
echo "╔══════════════════════════════════════════════════════════════╗"
|
||||
echo "║ SETUP COMPLETE - READY FOR DEPLOYMENT ║"
|
||||
echo "╚══════════════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
echo "✅ All configurations synced from .env file"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Review Terraform plan:"
|
||||
echo " cd infra/terraform && terraform plan"
|
||||
echo ""
|
||||
echo " 2. Deploy infrastructure:"
|
||||
echo " ./infra/scripts/azure-deploy.sh"
|
||||
echo ""
|
||||
echo " 3. After deployment, update Kubernetes secrets:"
|
||||
echo " ./infra/scripts/azure-update-k8s-secrets.sh"
|
||||
echo ""
|
||||
echo " 4. Deploy services to Kubernetes:"
|
||||
echo " kubectl apply -k infra/k8s/overlays/dev"
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Complete Azure deployment script
|
||||
# Uses environment variables from .env file
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
TERRAFORM_DIR="$PROJECT_ROOT/infra/terraform"
|
||||
|
||||
echo "🚀 Starting Azure deployment..."
|
||||
|
||||
# Load environment variables
|
||||
source "$SCRIPT_DIR/azure-load-env.sh"
|
||||
|
||||
# Change to Terraform directory
|
||||
cd "$TERRAFORM_DIR"
|
||||
|
||||
# Initialize Terraform
|
||||
echo "📦 Initializing Terraform..."
|
||||
terraform init
|
||||
|
||||
# Validate configuration
|
||||
echo "✅ Validating Terraform configuration..."
|
||||
terraform validate
|
||||
|
||||
# Plan deployment
|
||||
echo "📋 Planning deployment..."
|
||||
terraform plan -out=tfplan
|
||||
|
||||
# Ask for confirmation
|
||||
read -p "Do you want to apply these changes? (yes/no): " -r
|
||||
if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
|
||||
echo "Deployment cancelled."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Apply changes
|
||||
echo "🔨 Applying Terraform configuration..."
|
||||
terraform apply tfplan
|
||||
|
||||
# Get outputs
|
||||
echo ""
|
||||
echo "📊 Deployment outputs:"
|
||||
terraform output
|
||||
|
||||
# Save kubeconfig if AKS was created
|
||||
if terraform output -raw aks_kube_config > /dev/null 2>&1; then
|
||||
KUBECONFIG_FILE="$PROJECT_ROOT/.kube/config"
|
||||
mkdir -p "$(dirname "$KUBECONFIG_FILE")"
|
||||
terraform output -raw aks_kube_config > "$KUBECONFIG_FILE"
|
||||
echo ""
|
||||
echo "✅ Kubernetes config saved to: $KUBECONFIG_FILE"
|
||||
echo " You can now use: kubectl --kubeconfig=$KUBECONFIG_FILE get nodes"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "✅ Azure deployment complete!"
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Fix .env file to ensure proper ARM_* variable mapping for Terraform
|
||||
# Adds ARM_* aliases for AZURE_* variables if they don't exist
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
ENV_FILE="$PROJECT_ROOT/.env"
|
||||
BACKUP_FILE="${ENV_FILE}.backup.$(date +%Y%m%d_%H%M%S)"
|
||||
|
||||
if [ ! -f "$ENV_FILE" ]; then
|
||||
echo "❌ .env file not found at: $ENV_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "🔧 Fixing .env file variable mapping..."
|
||||
echo ""
|
||||
|
||||
# Backup original
|
||||
cp "$ENV_FILE" "$BACKUP_FILE"
|
||||
echo "✓ Backup created: $BACKUP_FILE"
|
||||
echo ""
|
||||
|
||||
# Load current values
|
||||
set -a
|
||||
source "$ENV_FILE"
|
||||
set +a
|
||||
|
||||
# Check what needs to be added
|
||||
ADDITIONS=()
|
||||
|
||||
if [ -n "$AZURE_SUBSCRIPTION_ID" ] && [ -z "$ARM_SUBSCRIPTION_ID" ]; then
|
||||
ADDITIONS+=("ARM_SUBSCRIPTION_ID=\"$AZURE_SUBSCRIPTION_ID\"")
|
||||
fi
|
||||
|
||||
if [ -n "$AZURE_TENANT_ID" ] && [ -z "$ARM_TENANT_ID" ]; then
|
||||
ADDITIONS+=("ARM_TENANT_ID=\"$AZURE_TENANT_ID\"")
|
||||
fi
|
||||
|
||||
if [ -n "$AZURE_LOCATION" ] && [ -z "$ARM_LOCATION" ]; then
|
||||
ADDITIONS+=("ARM_LOCATION=\"$AZURE_LOCATION\"")
|
||||
fi
|
||||
|
||||
# Add TF_VAR_environment if not set
|
||||
if [ -z "$TF_VAR_environment" ]; then
|
||||
ADDITIONS+=("TF_VAR_environment=\"dev\"")
|
||||
fi
|
||||
|
||||
if [ ${#ADDITIONS[@]} -eq 0 ]; then
|
||||
echo "✅ No fixes needed - all variables are properly mapped"
|
||||
rm -f "$BACKUP_FILE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Adding the following variables:"
|
||||
for var in "${ADDITIONS[@]}"; do
|
||||
echo " + $var"
|
||||
done
|
||||
echo ""
|
||||
|
||||
# Append to .env file
|
||||
echo "" >> "$ENV_FILE"
|
||||
echo "# Terraform ARM variables (auto-added by azure-fix-env-mapping.sh)" >> "$ENV_FILE"
|
||||
for var in "${ADDITIONS[@]}"; do
|
||||
echo "$var" >> "$ENV_FILE"
|
||||
done
|
||||
|
||||
echo "✅ .env file updated!"
|
||||
echo ""
|
||||
echo "Changes:"
|
||||
echo " • Added ${#ADDITIONS[@]} variable(s)"
|
||||
echo " • Backup saved to: $BACKUP_FILE"
|
||||
echo ""
|
||||
echo "To verify:"
|
||||
echo " ./infra/scripts/azure-validate-current-env.sh"
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Integrate existing Azure CDN configuration from azure-cdn-config.env
|
||||
# Updates .env file with CDN values if they exist
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
CDN_CONFIG="$PROJECT_ROOT/azure-cdn-config.env"
|
||||
ENV_FILE="$PROJECT_ROOT/.env"
|
||||
|
||||
echo "🔄 Integrating Azure CDN configuration..."
|
||||
|
||||
if [ -f "$CDN_CONFIG" ]; then
|
||||
echo "Found existing CDN configuration: $CDN_CONFIG"
|
||||
|
||||
# Load CDN config
|
||||
set -a
|
||||
source "$CDN_CONFIG"
|
||||
set +a
|
||||
|
||||
# Update .env file with CDN values if not already set
|
||||
if [ -f "$ENV_FILE" ]; then
|
||||
# Check if CDN values are already in .env
|
||||
if ! grep -q "AZURE_STORAGE_ACCOUNT=" "$ENV_FILE" 2>/dev/null; then
|
||||
echo "Adding CDN configuration to .env file..."
|
||||
cat >> "$ENV_FILE" << EOF
|
||||
|
||||
# Azure CDN Configuration (from azure-cdn-config.env)
|
||||
AZURE_STORAGE_ACCOUNT=${AZURE_STORAGE_ACCOUNT:-}
|
||||
AZURE_STORAGE_KEY=${AZURE_STORAGE_KEY:-}
|
||||
AZURE_STORAGE_CONTAINER=${AZURE_STORAGE_CONTAINER:-images}
|
||||
AZURE_RESOURCE_GROUP=${AZURE_RESOURCE_GROUP:-}
|
||||
AZURE_CDN_PROFILE=${AZURE_CDN_PROFILE:-}
|
||||
AZURE_CDN_ENDPOINT=${AZURE_CDN_ENDPOINT:-}
|
||||
CDN_BASE_URL=${CDN_BASE_URL:-}
|
||||
CDN_BASE_URL_BLOB=${CDN_BASE_URL_BLOB:-}
|
||||
CDN_BASE_URL_CDN=${CDN_BASE_URL_CDN:-}
|
||||
EOF
|
||||
echo "✅ CDN configuration added to .env"
|
||||
else
|
||||
echo "ℹ️ CDN configuration already exists in .env"
|
||||
fi
|
||||
else
|
||||
echo "⚠️ .env file not found. Creating from CDN config..."
|
||||
cp "$CDN_CONFIG" "$ENV_FILE"
|
||||
echo "✅ Created .env from CDN config"
|
||||
fi
|
||||
|
||||
# Export for Terraform
|
||||
export TF_VAR_storage_account_name="${AZURE_STORAGE_ACCOUNT}"
|
||||
export TF_VAR_cdn_profile_name="${AZURE_CDN_PROFILE}"
|
||||
export TF_VAR_cdn_endpoint_name="${AZURE_CDN_ENDPOINT}"
|
||||
|
||||
echo ""
|
||||
echo "CDN Configuration:"
|
||||
echo " Storage Account: ${AZURE_STORAGE_ACCOUNT}"
|
||||
echo " CDN Profile: ${AZURE_CDN_PROFILE}"
|
||||
echo " CDN Endpoint: ${AZURE_CDN_ENDPOINT}"
|
||||
echo " Base URL: ${CDN_BASE_URL}"
|
||||
else
|
||||
echo "ℹ️ No existing CDN configuration found at: $CDN_CONFIG"
|
||||
echo " CDN will be created by Terraform if needed"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "✅ CDN integration complete!"
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Load Azure environment variables from .env file
|
||||
# Usage: source infra/scripts/azure-load-env.sh
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
ENV_FILE="$PROJECT_ROOT/.env"
|
||||
TERRAFORM_ENV_FILE="$PROJECT_ROOT/infra/terraform/.env"
|
||||
|
||||
echo "🔧 Loading Azure environment variables..."
|
||||
|
||||
# Check for .env file in project root
|
||||
if [ -f "$ENV_FILE" ]; then
|
||||
echo "Loading from: $ENV_FILE"
|
||||
set -a
|
||||
source "$ENV_FILE"
|
||||
set +a
|
||||
elif [ -f "$TERRAFORM_ENV_FILE" ]; then
|
||||
echo "Loading from: $TERRAFORM_ENV_FILE"
|
||||
set -a
|
||||
source "$TERRAFORM_ENV_FILE"
|
||||
set +a
|
||||
else
|
||||
echo "⚠️ No .env file found. Looking for:"
|
||||
echo " - $ENV_FILE"
|
||||
echo " - $TERRAFORM_ENV_FILE"
|
||||
echo ""
|
||||
echo "Creating example file..."
|
||||
cp "$PROJECT_ROOT/infra/terraform/.env.example" "$TERRAFORM_ENV_FILE"
|
||||
echo "✅ Created $TERRAFORM_ENV_FILE"
|
||||
echo "Please edit it with your Azure credentials and run this script again."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Map AZURE_* to ARM_* if needed (for Terraform compatibility)
|
||||
if [ -n "$AZURE_SUBSCRIPTION_ID" ] && [ -z "$ARM_SUBSCRIPTION_ID" ]; then
|
||||
export ARM_SUBSCRIPTION_ID="$AZURE_SUBSCRIPTION_ID"
|
||||
fi
|
||||
|
||||
if [ -n "$AZURE_TENANT_ID" ] && [ -z "$ARM_TENANT_ID" ]; then
|
||||
export ARM_TENANT_ID="$AZURE_TENANT_ID"
|
||||
fi
|
||||
|
||||
if [ -n "$AZURE_LOCATION" ] && [ -z "$ARM_LOCATION" ]; then
|
||||
export ARM_LOCATION="$AZURE_LOCATION"
|
||||
fi
|
||||
|
||||
# Validate required variables (check both ARM_* and AZURE_*)
|
||||
SUBSCRIPTION_ID="${ARM_SUBSCRIPTION_ID:-$AZURE_SUBSCRIPTION_ID}"
|
||||
TENANT_ID="${ARM_TENANT_ID:-$AZURE_TENANT_ID}"
|
||||
|
||||
MISSING_VARS=()
|
||||
|
||||
if [ -z "$SUBSCRIPTION_ID" ]; then
|
||||
MISSING_VARS+=("ARM_SUBSCRIPTION_ID or AZURE_SUBSCRIPTION_ID")
|
||||
fi
|
||||
|
||||
if [ -z "$TENANT_ID" ]; then
|
||||
MISSING_VARS+=("ARM_TENANT_ID or AZURE_TENANT_ID")
|
||||
fi
|
||||
|
||||
if [ ${#MISSING_VARS[@]} -gt 0 ]; then
|
||||
echo "❌ Missing required environment variables:"
|
||||
for var in "${MISSING_VARS[@]}"; do
|
||||
echo " - $var"
|
||||
done
|
||||
echo ""
|
||||
echo "Please set these in your .env file."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Set Terraform variables from environment (use mapped values)
|
||||
export TF_VAR_subscription_id="${SUBSCRIPTION_ID}"
|
||||
export TF_VAR_tenant_id="${TENANT_ID}"
|
||||
export TF_VAR_client_id="${ARM_CLIENT_ID:-$AZURE_CLIENT_ID:-}"
|
||||
export TF_VAR_client_secret="${ARM_CLIENT_SECRET:-$AZURE_CLIENT_SECRET:-}"
|
||||
|
||||
# Set Azure CLI defaults if using CLI auth
|
||||
if [ -z "$ARM_CLIENT_ID" ] && [ -z "$AZURE_CLIENT_ID" ]; then
|
||||
echo "ℹ️ Using Azure CLI authentication (no service principal set)"
|
||||
az account set --subscription "$SUBSCRIPTION_ID" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
echo "✅ Environment variables loaded"
|
||||
echo ""
|
||||
echo "Azure Configuration:"
|
||||
echo " Subscription ID: ${SUBSCRIPTION_ID:0:8}...${SUBSCRIPTION_ID: -4}"
|
||||
echo " Tenant ID: ${TENANT_ID:0:8}...${TENANT_ID: -4}"
|
||||
echo " Location: ${ARM_LOCATION:-${AZURE_LOCATION:-westeurope}}"
|
||||
echo " Environment: ${TF_VAR_environment:-dev}"
|
||||
if [ -n "$AZURE_MANAGEMENT_GROUP_ID" ]; then
|
||||
echo " Management Group: $AZURE_MANAGEMENT_GROUP_ID"
|
||||
fi
|
||||
echo ""
|
||||
echo "You can now run Terraform commands."
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Azure Resource Provider Registration Script
|
||||
# Registers all required resource providers for The Order
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
# Colors
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Required Resource Providers
|
||||
REQUIRED_PROVIDERS=(
|
||||
"Microsoft.ContainerService" # AKS
|
||||
"Microsoft.KeyVault" # Key Vault
|
||||
"Microsoft.Storage" # Storage Accounts
|
||||
"Microsoft.Network" # Networking
|
||||
"Microsoft.Compute" # Compute resources
|
||||
"Microsoft.DBforPostgreSQL" # PostgreSQL
|
||||
"Microsoft.ContainerRegistry" # ACR
|
||||
"Microsoft.ManagedIdentity" # Managed Identities
|
||||
"Microsoft.Insights" # Application Insights, Monitor
|
||||
"Microsoft.Logic" # Logic Apps
|
||||
"Microsoft.OperationalInsights" # Log Analytics
|
||||
"Microsoft.Authorization" # RBAC
|
||||
"Microsoft.Resources" # Resource Manager
|
||||
)
|
||||
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo -e "${BLUE}Azure Resource Provider Registration${NC}"
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo ""
|
||||
|
||||
# Check if Azure CLI is installed
|
||||
if ! command -v az &> /dev/null; then
|
||||
echo -e "${RED}Error: Azure CLI is not installed.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if logged in
|
||||
if ! az account show &> /dev/null; then
|
||||
echo -e "${YELLOW}Please log in to Azure...${NC}"
|
||||
az login
|
||||
fi
|
||||
|
||||
SUBSCRIPTION_ID=$(az account show --query id -o tsv)
|
||||
SUBSCRIPTION_NAME=$(az account show --query name -o tsv)
|
||||
echo -e "${GREEN}Subscription: ${SUBSCRIPTION_NAME} (${SUBSCRIPTION_ID})${NC}"
|
||||
echo ""
|
||||
|
||||
# Check current registration status
|
||||
echo -e "${YELLOW}Checking current registration status...${NC}"
|
||||
echo ""
|
||||
|
||||
UNREGISTERED=()
|
||||
ALREADY_REGISTERED=()
|
||||
REGISTERING=()
|
||||
|
||||
for provider in "${REQUIRED_PROVIDERS[@]}"; do
|
||||
STATUS=$(az provider show --namespace "${provider}" --query "registrationState" -o tsv 2>/dev/null || echo "NotRegistered")
|
||||
|
||||
if [ "${STATUS}" == "Registered" ]; then
|
||||
echo -e "${GREEN}✓ ${provider} - Already Registered${NC}"
|
||||
ALREADY_REGISTERED+=("${provider}")
|
||||
elif [ "${STATUS}" == "Registering" ]; then
|
||||
echo -e "${YELLOW}⏳ ${provider} - Currently Registering${NC}"
|
||||
REGISTERING+=("${provider}")
|
||||
else
|
||||
echo -e "${RED}✗ ${provider} - Not Registered${NC}"
|
||||
UNREGISTERED+=("${provider}")
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
# Register unregistered providers
|
||||
if [ ${#UNREGISTERED[@]} -gt 0 ]; then
|
||||
echo -e "${YELLOW}Registering ${#UNREGISTERED[@]} unregistered provider(s)...${NC}"
|
||||
echo ""
|
||||
|
||||
for provider in "${UNREGISTERED[@]}"; do
|
||||
echo -n "Registering ${provider}... "
|
||||
az provider register --namespace "${provider}" --wait
|
||||
echo -e "${GREEN}✓ Registered${NC}"
|
||||
done
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Wait for providers that are currently registering
|
||||
if [ ${#REGISTERING[@]} -gt 0 ]; then
|
||||
echo -e "${YELLOW}Waiting for ${#REGISTERING[@]} provider(s) to finish registering...${NC}"
|
||||
echo ""
|
||||
|
||||
for provider in "${REGISTERING[@]}"; do
|
||||
echo -n "Waiting for ${provider}... "
|
||||
az provider register --namespace "${provider}" --wait
|
||||
echo -e "${GREEN}✓ Registered${NC}"
|
||||
done
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Final status check
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo -e "${BLUE}Final Registration Status${NC}"
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo ""
|
||||
|
||||
ALL_REGISTERED=true
|
||||
for provider in "${REQUIRED_PROVIDERS[@]}"; do
|
||||
STATUS=$(az provider show --namespace "${provider}" --query "registrationState" -o tsv)
|
||||
|
||||
if [ "${STATUS}" == "Registered" ]; then
|
||||
echo -e "${GREEN}✓ ${provider}${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ ${provider} - Status: ${STATUS}${NC}"
|
||||
ALL_REGISTERED=false
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
if [ "${ALL_REGISTERED}" = true ]; then
|
||||
echo -e "${GREEN}✓ All required resource providers are registered!${NC}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${YELLOW}⚠ Some providers are not yet registered. Please wait and run this script again.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -1,254 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Azure Setup Script for The Order
|
||||
# This script sets up Azure prerequisites including:
|
||||
# - Listing available regions (excluding US)
|
||||
# - Checking and registering required resource providers
|
||||
# - Checking quotas for all regions
|
||||
# - Setting default region to West Europe
|
||||
#
|
||||
|
||||
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
|
||||
|
||||
# Default region
|
||||
DEFAULT_REGION="westeurope"
|
||||
|
||||
# Required Resource Providers
|
||||
REQUIRED_PROVIDERS=(
|
||||
"Microsoft.ContainerService" # AKS
|
||||
"Microsoft.KeyVault" # Key Vault
|
||||
"Microsoft.Storage" # Storage Accounts
|
||||
"Microsoft.Network" # Networking
|
||||
"Microsoft.Compute" # Compute resources
|
||||
"Microsoft.DBforPostgreSQL" # PostgreSQL
|
||||
"Microsoft.ContainerRegistry" # ACR
|
||||
"Microsoft.ManagedIdentity" # Managed Identities
|
||||
"Microsoft.Insights" # Application Insights, Monitor
|
||||
"Microsoft.Logic" # Logic Apps
|
||||
"Microsoft.OperationalInsights" # Log Analytics
|
||||
"Microsoft.Authorization" # RBAC
|
||||
"Microsoft.Resources" # Resource Manager
|
||||
)
|
||||
|
||||
# Preview Features (if needed)
|
||||
PREVIEW_FEATURES=(
|
||||
# Add preview features here if needed
|
||||
# Example: "Microsoft.ContainerService/EnableWorkloadIdentityPreview"
|
||||
)
|
||||
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo -e "${BLUE}Azure Setup for The Order${NC}"
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo ""
|
||||
|
||||
# Check if Azure CLI is installed
|
||||
if ! command -v az &> /dev/null; then
|
||||
echo -e "${RED}Error: Azure CLI is not installed.${NC}"
|
||||
echo "Please install it from: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if logged in
|
||||
echo -e "${YELLOW}Checking Azure CLI login status...${NC}"
|
||||
if ! az account show &> /dev/null; then
|
||||
echo -e "${YELLOW}Not logged in. Please log in...${NC}"
|
||||
az login
|
||||
fi
|
||||
|
||||
# Get current subscription
|
||||
SUBSCRIPTION_ID=$(az account show --query id -o tsv)
|
||||
SUBSCRIPTION_NAME=$(az account show --query name -o tsv)
|
||||
echo -e "${GREEN}Current Subscription: ${SUBSCRIPTION_NAME} (${SUBSCRIPTION_ID})${NC}"
|
||||
echo ""
|
||||
|
||||
# Set default region
|
||||
echo -e "${BLUE}Setting default region to: ${DEFAULT_REGION}${NC}"
|
||||
export AZURE_DEFAULT_REGION=${DEFAULT_REGION}
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 1. List All Azure Commercial Regions (Excluding US)
|
||||
# ============================================
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo -e "${BLUE}1. Available Azure Commercial Regions${NC}"
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo ""
|
||||
|
||||
# Get all locations and filter out US regions
|
||||
echo -e "${YELLOW}Fetching available regions (excluding US)...${NC}"
|
||||
az account list-locations \
|
||||
--query "[?metadata.regionType=='Physical' && !contains(name, 'us')].{Name:name, DisplayName:displayName, RegionalDisplayName:regionalDisplayName}" \
|
||||
-o table
|
||||
|
||||
echo ""
|
||||
echo -e "${YELLOW}Recommended regions for The Order:${NC}"
|
||||
echo " - westeurope (Primary - Default)"
|
||||
echo " - northeurope (Secondary)"
|
||||
echo " - uksouth (UK)"
|
||||
echo " - switzerlandnorth (Switzerland)"
|
||||
echo " - norwayeast (Norway)"
|
||||
echo ""
|
||||
|
||||
# Save regions to file
|
||||
REGIONS_FILE="azure-regions.txt"
|
||||
az account list-locations \
|
||||
--query "[?metadata.regionType=='Physical' && !contains(name, 'us')].name" \
|
||||
-o tsv > "${REGIONS_FILE}"
|
||||
echo -e "${GREEN}Regions list saved to: ${REGIONS_FILE}${NC}"
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 2. List Required Resource Providers
|
||||
# ============================================
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo -e "${BLUE}2. Required Resource Providers${NC}"
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo ""
|
||||
|
||||
echo -e "${YELLOW}Required Resource Providers:${NC}"
|
||||
for provider in "${REQUIRED_PROVIDERS[@]}"; do
|
||||
echo " - ${provider}"
|
||||
done
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 3. Check and Register Resource Providers
|
||||
# ============================================
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo -e "${BLUE}3. Checking Resource Provider Registration${NC}"
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo ""
|
||||
|
||||
UNREGISTERED_PROVIDERS=()
|
||||
|
||||
for provider in "${REQUIRED_PROVIDERS[@]}"; do
|
||||
echo -n "Checking ${provider}... "
|
||||
STATUS=$(az provider show --namespace "${provider}" --query "registrationState" -o tsv 2>/dev/null || echo "NotRegistered")
|
||||
|
||||
if [ "${STATUS}" == "Registered" ]; then
|
||||
echo -e "${GREEN}✓ Registered${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}✗ Not Registered${NC}"
|
||||
UNREGISTERED_PROVIDERS+=("${provider}")
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
|
||||
# Register unregistered providers
|
||||
if [ ${#UNREGISTERED_PROVIDERS[@]} -gt 0 ]; then
|
||||
echo -e "${YELLOW}Registering unregistered providers...${NC}"
|
||||
for provider in "${UNREGISTERED_PROVIDERS[@]}"; do
|
||||
echo -n "Registering ${provider}... "
|
||||
az provider register --namespace "${provider}" --wait
|
||||
echo -e "${GREEN}✓ Registered${NC}"
|
||||
done
|
||||
echo ""
|
||||
else
|
||||
echo -e "${GREEN}All required providers are already registered!${NC}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# ============================================
|
||||
# 4. Check Preview Features
|
||||
# ============================================
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo -e "${BLUE}4. Preview Features${NC}"
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo ""
|
||||
|
||||
if [ ${#PREVIEW_FEATURES[@]} -gt 0 ]; then
|
||||
echo -e "${YELLOW}Required Preview Features:${NC}"
|
||||
for feature in "${PREVIEW_FEATURES[@]}"; do
|
||||
echo " - ${feature}"
|
||||
done
|
||||
echo ""
|
||||
|
||||
echo -e "${YELLOW}Note: Preview features may need to be enabled manually in Azure Portal${NC}"
|
||||
echo ""
|
||||
else
|
||||
echo -e "${GREEN}No preview features required.${NC}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# ============================================
|
||||
# 5. Check Quotas for All Regions
|
||||
# ============================================
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo -e "${BLUE}5. Checking Quotas for All Regions${NC}"
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo ""
|
||||
|
||||
# Read regions from file
|
||||
REGIONS=$(cat "${REGIONS_FILE}")
|
||||
|
||||
# Quota types to check
|
||||
QUOTA_TYPES=(
|
||||
"cores" # VM cores
|
||||
"virtualMachines" # VM instances
|
||||
)
|
||||
|
||||
# Primary regions to check in detail
|
||||
PRIMARY_REGIONS=("westeurope" "northeurope" "uksouth")
|
||||
|
||||
echo -e "${YELLOW}Checking quotas for primary regions...${NC}"
|
||||
echo ""
|
||||
|
||||
QUOTA_FILE="azure-quotas.txt"
|
||||
> "${QUOTA_FILE}" # Clear file
|
||||
|
||||
for region in "${PRIMARY_REGIONS[@]}"; do
|
||||
echo -e "${BLUE}Region: ${region}${NC}"
|
||||
echo "----------------------------------------"
|
||||
|
||||
# Get VM family quotas
|
||||
echo "VM Family Quotas:"
|
||||
az vm list-usage \
|
||||
--location "${region}" \
|
||||
--query "[].{Name:name.value, CurrentValue:currentValue, Limit:limit}" \
|
||||
-o table 2>/dev/null || echo " Unable to fetch VM quotas"
|
||||
|
||||
echo "" >> "${QUOTA_FILE}"
|
||||
echo "Region: ${region}" >> "${QUOTA_FILE}"
|
||||
echo "----------------------------------------" >> "${QUOTA_FILE}"
|
||||
az vm list-usage --location "${region}" -o table >> "${QUOTA_FILE}" 2>/dev/null || true
|
||||
echo "" >> "${QUOTA_FILE}"
|
||||
|
||||
# Get storage account quota
|
||||
echo "Storage Account Quota:"
|
||||
STORAGE_QUOTA=$(az storage account show-usage \
|
||||
--location "${region}" \
|
||||
--query "{CurrentValue:currentValue, Limit:limit}" \
|
||||
-o json 2>/dev/null || echo '{"CurrentValue": "N/A", "Limit": "N/A"}')
|
||||
echo "${STORAGE_QUOTA}" | jq '.' 2>/dev/null || echo "${STORAGE_QUOTA}"
|
||||
|
||||
echo ""
|
||||
done
|
||||
|
||||
echo -e "${GREEN}Detailed quota information saved to: ${QUOTA_FILE}${NC}"
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 6. Summary
|
||||
# ============================================
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo -e "${BLUE}Setup Summary${NC}"
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo ""
|
||||
echo -e "${GREEN}✓ Default region set to: ${DEFAULT_REGION}${NC}"
|
||||
echo -e "${GREEN}✓ Available regions listed (excluding US)${NC}"
|
||||
echo -e "${GREEN}✓ Resource providers checked and registered${NC}"
|
||||
echo -e "${GREEN}✓ Quotas checked for primary regions${NC}"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Next Steps:${NC}"
|
||||
echo " 1. Review quota limits in ${QUOTA_FILE}"
|
||||
echo " 2. Update Terraform variables with region: ${DEFAULT_REGION}"
|
||||
echo " 3. Proceed with infrastructure deployment"
|
||||
echo ""
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Sync environment variables from .env to Terraform variables
|
||||
# Ensures Terraform uses values from .env file
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
ENV_FILE="$PROJECT_ROOT/.env"
|
||||
TERRAFORM_DIR="$PROJECT_ROOT/infra/terraform"
|
||||
|
||||
echo "🔄 Syncing environment variables to Terraform..."
|
||||
|
||||
# Load and validate environment
|
||||
source "$SCRIPT_DIR/azure-validate-env.sh"
|
||||
|
||||
# Create terraform.tfvars from environment variables
|
||||
TFVARS_FILE="$TERRAFORM_DIR/terraform.tfvars"
|
||||
|
||||
cat > "$TFVARS_FILE" << EOF
|
||||
# Terraform variables generated from .env file
|
||||
# DO NOT EDIT MANUALLY - regenerated by azure-sync-env-to-terraform.sh
|
||||
# Last updated: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
|
||||
|
||||
# Azure Configuration
|
||||
azure_region = "${ARM_LOCATION:-westeurope}"
|
||||
environment = "${TF_VAR_environment:-dev}"
|
||||
project_name = "the-order"
|
||||
|
||||
# Azure Authentication (sensitive - use environment variables)
|
||||
# subscription_id = "${ARM_SUBSCRIPTION_ID}"
|
||||
# tenant_id = "${ARM_TENANT_ID}"
|
||||
# client_id = "${ARM_CLIENT_ID:-}"
|
||||
# client_secret = "${ARM_CLIENT_SECRET:-}"
|
||||
|
||||
# Resource Naming
|
||||
resource_group_name = "${TF_VAR_resource_group_name}"
|
||||
storage_account_name = "${TF_VAR_storage_account_name}"
|
||||
key_vault_name = "${TF_VAR_key_vault_name}"
|
||||
|
||||
# AKS Configuration
|
||||
aks_cluster_name = "${TF_VAR_aks_cluster_name:-the-order-aks-${TF_VAR_environment:-dev}}"
|
||||
aks_node_count = ${TF_VAR_aks_node_count:-2}
|
||||
aks_vm_size = "${TF_VAR_aks_vm_size:-Standard_B2s}"
|
||||
|
||||
# Database Configuration
|
||||
database_name = "${TF_VAR_database_name:-the-order-db-${TF_VAR_environment:-dev}}"
|
||||
database_admin_user = "${TF_VAR_database_admin_user:-theorder_admin}"
|
||||
|
||||
# Tags
|
||||
tags = {
|
||||
Environment = "${TF_VAR_environment:-dev}"
|
||||
Project = "the-order"
|
||||
ManagedBy = "terraform"
|
||||
CreatedBy = "azure-sync-env-to-terraform.sh"
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "✅ Terraform variables synced to: $TFVARS_FILE"
|
||||
echo ""
|
||||
echo "You can now run Terraform commands:"
|
||||
echo " cd $TERRAFORM_DIR"
|
||||
echo " terraform init"
|
||||
echo " terraform plan"
|
||||
echo " terraform apply"
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Update Kubernetes secrets from Azure Key Vault
|
||||
# Uses values from .env file to configure External Secrets
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
echo "🔄 Updating Kubernetes secrets configuration from .env..."
|
||||
|
||||
# Load environment
|
||||
source "$SCRIPT_DIR/azure-validate-env.sh"
|
||||
|
||||
# Get Key Vault URI from Terraform output if available
|
||||
cd "$PROJECT_ROOT/infra/terraform"
|
||||
if terraform output -json key_vault_uri &> /dev/null; then
|
||||
KEY_VAULT_URI=$(terraform output -raw key_vault_uri)
|
||||
echo "Found Key Vault URI from Terraform: $KEY_VAULT_URI"
|
||||
else
|
||||
# Construct from known values
|
||||
KEY_VAULT_NAME="${TF_VAR_key_vault_name:-the-order-kv-${TF_VAR_environment:-dev}}"
|
||||
KEY_VAULT_URI="https://${KEY_VAULT_NAME}.vault.azure.net/"
|
||||
echo "Using constructed Key Vault URI: $KEY_VAULT_URI"
|
||||
fi
|
||||
|
||||
# Update External Secrets configuration
|
||||
EXTERNAL_SECRETS_FILE="$PROJECT_ROOT/infra/k8s/base/external-secrets.yaml"
|
||||
|
||||
# Use sed or create a template update
|
||||
if [ -f "$EXTERNAL_SECRETS_FILE" ]; then
|
||||
# Create updated version
|
||||
sed -i.bak "s|tenantId: \"\"|tenantId: \"${ARM_TENANT_ID}\"|g" "$EXTERNAL_SECRETS_FILE"
|
||||
sed -i.bak "s|vaultUrl: \"\"|vaultUrl: \"${KEY_VAULT_URI}\"|g" "$EXTERNAL_SECRETS_FILE"
|
||||
rm -f "${EXTERNAL_SECRETS_FILE}.bak"
|
||||
echo "✅ Updated External Secrets configuration"
|
||||
else
|
||||
echo "⚠️ External Secrets file not found: $EXTERNAL_SECRETS_FILE"
|
||||
fi
|
||||
|
||||
# Update Azure ConfigMap
|
||||
CONFIGMAP_FILE="$PROJECT_ROOT/infra/k8s/base/configmap-azure.yaml"
|
||||
|
||||
if [ -f "$CONFIGMAP_FILE" ]; then
|
||||
# Update with actual values (non-sensitive)
|
||||
sed -i.bak "s|AZURE_REGION: \".*\"|AZURE_REGION: \"${ARM_LOCATION:-westeurope}\"|g" "$CONFIGMAP_FILE"
|
||||
sed -i.bak "s|AKS_RESOURCE_GROUP: \".*\"|AKS_RESOURCE_GROUP: \"${TF_VAR_resource_group_name}\"|g" "$CONFIGMAP_FILE"
|
||||
rm -f "${CONFIGMAP_FILE}.bak"
|
||||
echo "✅ Updated Azure ConfigMap"
|
||||
else
|
||||
echo "⚠️ ConfigMap file not found: $CONFIGMAP_FILE"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "✅ Kubernetes secrets configuration updated!"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Review updated files:"
|
||||
echo " - $EXTERNAL_SECRETS_FILE"
|
||||
echo " - $CONFIGMAP_FILE"
|
||||
echo " 2. Apply to Kubernetes:"
|
||||
echo " kubectl apply -f $EXTERNAL_SECRETS_FILE"
|
||||
echo " kubectl apply -f $CONFIGMAP_FILE"
|
||||
|
||||
@@ -1,188 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Validate current .env file against Azure deployment requirements
|
||||
# Provides detailed analysis and recommendations
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
ENV_FILE="$PROJECT_ROOT/.env"
|
||||
|
||||
echo "╔══════════════════════════════════════════════════════════════╗"
|
||||
echo "║ .ENV FILE ANALYSIS FOR AZURE DEPLOYMENTS ║"
|
||||
echo "╚══════════════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
|
||||
if [ ! -f "$ENV_FILE" ]; then
|
||||
echo "❌ .env file not found at: $ENV_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "📄 Analyzing: $ENV_FILE"
|
||||
echo ""
|
||||
|
||||
# Load environment
|
||||
set -a
|
||||
source "$ENV_FILE"
|
||||
set +a
|
||||
|
||||
# Check required variables
|
||||
echo "✅ REQUIRED VARIABLES:"
|
||||
echo ""
|
||||
|
||||
# Subscription ID
|
||||
if [ -n "$AZURE_SUBSCRIPTION_ID" ] || [ -n "$ARM_SUBSCRIPTION_ID" ]; then
|
||||
SUB_ID="${AZURE_SUBSCRIPTION_ID:-$ARM_SUBSCRIPTION_ID}"
|
||||
echo " ✓ Subscription ID: ${SUB_ID:0:8}...${SUB_ID: -4}"
|
||||
if [[ ! "$SUB_ID" =~ ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ ]]; then
|
||||
echo " ⚠️ Warning: Format may be invalid (should be UUID)"
|
||||
fi
|
||||
else
|
||||
echo " ❌ Subscription ID: MISSING"
|
||||
fi
|
||||
|
||||
# Tenant ID
|
||||
if [ -n "$AZURE_TENANT_ID" ] || [ -n "$ARM_TENANT_ID" ]; then
|
||||
TENANT_ID="${AZURE_TENANT_ID:-$ARM_TENANT_ID}"
|
||||
echo " ✓ Tenant ID: ${TENANT_ID:0:8}...${TENANT_ID: -4}"
|
||||
if [[ ! "$TENANT_ID" =~ ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ ]]; then
|
||||
echo " ⚠️ Warning: Format may be invalid (should be UUID)"
|
||||
fi
|
||||
else
|
||||
echo " ❌ Tenant ID: MISSING"
|
||||
fi
|
||||
|
||||
# Location
|
||||
if [ -n "$AZURE_LOCATION" ] || [ -n "$ARM_LOCATION" ]; then
|
||||
LOCATION="${AZURE_LOCATION:-$ARM_LOCATION}"
|
||||
echo " ✓ Location: $LOCATION"
|
||||
if [[ "$LOCATION" =~ ^us ]]; then
|
||||
echo " ❌ ERROR: US regions are not allowed!"
|
||||
fi
|
||||
else
|
||||
echo " ⚠️ Location: NOT SET (will default to westeurope)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "📋 OPTIONAL BUT RECOMMENDED:"
|
||||
echo ""
|
||||
|
||||
# Management Group
|
||||
if [ -n "$AZURE_MANAGEMENT_GROUP_ID" ]; then
|
||||
echo " ✓ Management Group: $AZURE_MANAGEMENT_GROUP_ID"
|
||||
else
|
||||
echo " ○ Management Group: Not set"
|
||||
fi
|
||||
|
||||
# Resource Group
|
||||
if [ -n "$AZURE_RESOURCE_GROUP" ] || [ -n "$TF_VAR_resource_group_name" ]; then
|
||||
RG="${AZURE_RESOURCE_GROUP:-$TF_VAR_resource_group_name}"
|
||||
echo " ✓ Resource Group: $RG"
|
||||
else
|
||||
echo " ⚠️ Resource Group: Not set (will use default naming convention)"
|
||||
fi
|
||||
|
||||
# Environment
|
||||
if [ -n "$TF_VAR_environment" ]; then
|
||||
echo " ✓ Environment: $TF_VAR_environment"
|
||||
else
|
||||
echo " ⚠️ Environment: Not set (will default to 'dev')"
|
||||
fi
|
||||
|
||||
# Storage Account
|
||||
if [ -n "$TF_VAR_storage_account_name" ] || [ -n "$AZURE_STORAGE_ACCOUNT" ]; then
|
||||
SA="${TF_VAR_storage_account_name:-$AZURE_STORAGE_ACCOUNT}"
|
||||
echo " ✓ Storage Account: $SA"
|
||||
else
|
||||
echo " ⚠️ Storage Account: Not set (will use default naming)"
|
||||
fi
|
||||
|
||||
# Key Vault
|
||||
if [ -n "$TF_VAR_key_vault_name" ] || [ -n "$AZURE_KEY_VAULT_NAME" ]; then
|
||||
KV="${TF_VAR_key_vault_name:-$AZURE_KEY_VAULT_NAME}"
|
||||
echo " ✓ Key Vault: $KV"
|
||||
else
|
||||
echo " ⚠️ Key Vault: Not set (will use default naming)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "🔧 TERRAFORM VARIABLE MAPPING:"
|
||||
echo ""
|
||||
|
||||
# Check if variables need to be mapped
|
||||
NEEDS_MAPPING=false
|
||||
|
||||
if [ -n "$AZURE_SUBSCRIPTION_ID" ] && [ -z "$ARM_SUBSCRIPTION_ID" ]; then
|
||||
echo " ⚠️ AZURE_SUBSCRIPTION_ID found, but Terraform expects ARM_SUBSCRIPTION_ID"
|
||||
echo " Recommendation: Add ARM_SUBSCRIPTION_ID=\"$AZURE_SUBSCRIPTION_ID\""
|
||||
NEEDS_MAPPING=true
|
||||
fi
|
||||
|
||||
if [ -n "$AZURE_TENANT_ID" ] && [ -z "$ARM_TENANT_ID" ]; then
|
||||
echo " ⚠️ AZURE_TENANT_ID found, but Terraform expects ARM_TENANT_ID"
|
||||
echo " Recommendation: Add ARM_TENANT_ID=\"$AZURE_TENANT_ID\""
|
||||
NEEDS_MAPPING=true
|
||||
fi
|
||||
|
||||
if [ -n "$AZURE_LOCATION" ] && [ -z "$ARM_LOCATION" ]; then
|
||||
echo " ⚠️ AZURE_LOCATION found, but Terraform expects ARM_LOCATION"
|
||||
echo " Recommendation: Add ARM_LOCATION=\"$AZURE_LOCATION\""
|
||||
NEEDS_MAPPING=true
|
||||
fi
|
||||
|
||||
if [ "$NEEDS_MAPPING" = false ]; then
|
||||
echo " ✓ All variables properly mapped for Terraform"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "📊 SUMMARY:"
|
||||
echo ""
|
||||
|
||||
# Count issues
|
||||
ISSUES=0
|
||||
WARNINGS=0
|
||||
|
||||
if [ -z "$AZURE_SUBSCRIPTION_ID" ] && [ -z "$ARM_SUBSCRIPTION_ID" ]; then
|
||||
ISSUES=$((ISSUES + 1))
|
||||
fi
|
||||
|
||||
if [ -z "$AZURE_TENANT_ID" ] && [ -z "$ARM_TENANT_ID" ]; then
|
||||
ISSUES=$((ISSUES + 1))
|
||||
fi
|
||||
|
||||
if [ -z "$AZURE_LOCATION" ] && [ -z "$ARM_LOCATION" ]; then
|
||||
WARNINGS=$((WARNINGS + 1))
|
||||
fi
|
||||
|
||||
if [ "$ISSUES" -eq 0 ] && [ "$WARNINGS" -eq 0 ]; then
|
||||
echo " ✅ .env file is properly configured for Azure deployments"
|
||||
elif [ "$ISSUES" -eq 0 ]; then
|
||||
echo " ⚠️ .env file is mostly configured ($WARNINGS warning(s))"
|
||||
else
|
||||
echo " ❌ .env file has $ISSUES critical issue(s) and $WARNINGS warning(s)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "💡 RECOMMENDATIONS:"
|
||||
echo ""
|
||||
|
||||
if [ "$NEEDS_MAPPING" = true ]; then
|
||||
echo " 1. Add ARM_* variables for Terraform compatibility"
|
||||
echo " (Our scripts will auto-map, but explicit is better)"
|
||||
fi
|
||||
|
||||
if [ -z "$TF_VAR_environment" ]; then
|
||||
echo " 2. Add TF_VAR_environment=\"dev\" (or stage/prod)"
|
||||
fi
|
||||
|
||||
if [ -z "$TF_VAR_resource_group_name" ] && [ -z "$AZURE_RESOURCE_GROUP" ]; then
|
||||
echo " 3. Consider setting TF_VAR_resource_group_name for custom naming"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "✅ Analysis complete!"
|
||||
echo ""
|
||||
echo "To use with Azure deployments:"
|
||||
echo " source infra/scripts/azure-validate-env.sh"
|
||||
echo " ./infra/scripts/azure-complete-setup.sh"
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Validate Azure environment variables from .env file
|
||||
# Ensures all required variables are set for deployments
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
ENV_FILE="$PROJECT_ROOT/.env"
|
||||
|
||||
echo "🔍 Validating Azure environment configuration..."
|
||||
|
||||
# Load environment file if it exists
|
||||
if [ -f "$ENV_FILE" ]; then
|
||||
echo "Loading environment from: $ENV_FILE"
|
||||
set -a
|
||||
source "$ENV_FILE"
|
||||
set +a
|
||||
else
|
||||
echo "⚠️ No .env file found at: $ENV_FILE"
|
||||
echo "Creating from example..."
|
||||
if [ -f "$PROJECT_ROOT/infra/terraform/.env.example" ]; then
|
||||
cp "$PROJECT_ROOT/infra/terraform/.env.example" "$ENV_FILE"
|
||||
echo "✅ Created $ENV_FILE - please fill in your values"
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Required Azure variables
|
||||
REQUIRED_VARS=(
|
||||
"ARM_SUBSCRIPTION_ID"
|
||||
"ARM_TENANT_ID"
|
||||
)
|
||||
|
||||
# Optional but recommended
|
||||
RECOMMENDED_VARS=(
|
||||
"ARM_LOCATION"
|
||||
"TF_VAR_environment"
|
||||
"TF_VAR_resource_group_name"
|
||||
"TF_VAR_storage_account_name"
|
||||
"TF_VAR_key_vault_name"
|
||||
)
|
||||
|
||||
# Check required variables
|
||||
MISSING_REQUIRED=()
|
||||
for var in "${REQUIRED_VARS[@]}"; do
|
||||
if [ -z "${!var}" ]; then
|
||||
MISSING_REQUIRED+=("$var")
|
||||
fi
|
||||
done
|
||||
|
||||
# Check recommended variables
|
||||
MISSING_RECOMMENDED=()
|
||||
for var in "${RECOMMENDED_VARS[@]}"; do
|
||||
if [ -z "${!var}" ]; then
|
||||
MISSING_RECOMMENDED+=("$var")
|
||||
fi
|
||||
done
|
||||
|
||||
# Report results
|
||||
if [ ${#MISSING_REQUIRED[@]} -gt 0 ]; then
|
||||
echo "❌ Missing required variables:"
|
||||
for var in "${MISSING_REQUIRED[@]}"; do
|
||||
echo " - $var"
|
||||
done
|
||||
echo ""
|
||||
echo "Please set these in your .env file."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ${#MISSING_RECOMMENDED[@]} -gt 0 ]; then
|
||||
echo "⚠️ Missing recommended variables (will use defaults):"
|
||||
for var in "${MISSING_RECOMMENDED[@]}"; do
|
||||
echo " - $var"
|
||||
done
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Validate Azure CLI authentication
|
||||
if command -v az &> /dev/null; then
|
||||
if az account show &> /dev/null; then
|
||||
CURRENT_SUB=$(az account show --query id -o tsv)
|
||||
if [ "$CURRENT_SUB" != "$ARM_SUBSCRIPTION_ID" ]; then
|
||||
echo "⚠️ Azure CLI subscription ($CURRENT_SUB) differs from ARM_SUBSCRIPTION_ID"
|
||||
echo " Setting Azure CLI to use: $ARM_SUBSCRIPTION_ID"
|
||||
az account set --subscription "$ARM_SUBSCRIPTION_ID" || true
|
||||
fi
|
||||
else
|
||||
echo "⚠️ Not logged in to Azure CLI. Run: az login"
|
||||
fi
|
||||
else
|
||||
echo "⚠️ Azure CLI not installed. Install from: https://aka.ms/InstallAzureCLIDeb"
|
||||
fi
|
||||
|
||||
# Set defaults for missing recommended vars
|
||||
export ARM_LOCATION="${ARM_LOCATION:-westeurope}"
|
||||
export TF_VAR_environment="${TF_VAR_environment:-dev}"
|
||||
export TF_VAR_azure_region="${ARM_LOCATION}"
|
||||
|
||||
# Export Terraform variables
|
||||
export TF_VAR_subscription_id="${ARM_SUBSCRIPTION_ID}"
|
||||
export TF_VAR_tenant_id="${ARM_TENANT_ID}"
|
||||
export TF_VAR_client_id="${ARM_CLIENT_ID:-}"
|
||||
export TF_VAR_client_secret="${ARM_CLIENT_SECRET:-}"
|
||||
|
||||
# Generate resource names if not set
|
||||
if [ -z "$TF_VAR_resource_group_name" ]; then
|
||||
export TF_VAR_resource_group_name="the-order-rg-${TF_VAR_environment}"
|
||||
fi
|
||||
|
||||
if [ -z "$TF_VAR_storage_account_name" ]; then
|
||||
# Generate unique storage account name
|
||||
TIMESTAMP=$(date +%s | tail -c 5)
|
||||
export TF_VAR_storage_account_name="theorder${TF_VAR_environment}${TIMESTAMP}"
|
||||
fi
|
||||
|
||||
if [ -z "$TF_VAR_key_vault_name" ]; then
|
||||
export TF_VAR_key_vault_name="the-order-kv-${TF_VAR_environment}"
|
||||
fi
|
||||
|
||||
echo "✅ Environment validation complete!"
|
||||
echo ""
|
||||
echo "Azure Configuration:"
|
||||
echo " Subscription ID: ${ARM_SUBSCRIPTION_ID:0:8}..."
|
||||
echo " Tenant ID: ${ARM_TENANT_ID:0:8}..."
|
||||
echo " Location: ${ARM_LOCATION}"
|
||||
echo " Environment: ${TF_VAR_environment}"
|
||||
echo " Resource Group: ${TF_VAR_resource_group_name}"
|
||||
echo " Storage Account: ${TF_VAR_storage_account_name}"
|
||||
echo " Key Vault: ${TF_VAR_key_vault_name}"
|
||||
echo ""
|
||||
echo "All Terraform variables are set and ready for deployment."
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Deploy Cloud for Sovereignty Landing Zone
|
||||
# Uses Well-Architected Framework principles
|
||||
# Deploys across all non-US commercial Azure regions
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
TERRAFORM_DIR="$PROJECT_ROOT/infra/terraform"
|
||||
|
||||
echo "╔══════════════════════════════════════════════════════════════╗"
|
||||
echo "║ CLOUD FOR SOVEREIGNTY LANDING ZONE DEPLOYMENT ║"
|
||||
echo "╚══════════════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
|
||||
# Load environment
|
||||
source "$SCRIPT_DIR/azure-load-env.sh"
|
||||
|
||||
# Get management group ID from .env or use default
|
||||
MANAGEMENT_GROUP_ID="${AZURE_MANAGEMENT_GROUP_ID:-SOVEREIGN-ORDER-OF-HOSPITALLERS}"
|
||||
ENVIRONMENT="${TF_VAR_environment:-dev}"
|
||||
|
||||
echo "Configuration:"
|
||||
echo " Management Group: $MANAGEMENT_GROUP_ID"
|
||||
echo " Environment: $ENVIRONMENT"
|
||||
echo " Subscription: ${ARM_SUBSCRIPTION_ID:0:8}..."
|
||||
echo ""
|
||||
|
||||
# Confirm deployment
|
||||
read -p "Deploy landing zone to all non-US commercial regions? (yes/no): " -r
|
||||
if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
|
||||
echo "Deployment cancelled."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cd "$TERRAFORM_DIR"
|
||||
|
||||
# Step 1: Deploy Management Group Hierarchy
|
||||
echo ""
|
||||
echo "Step 1: Deploying Management Group Hierarchy..."
|
||||
cd management-groups
|
||||
terraform init
|
||||
terraform plan -var="management_group_id=$MANAGEMENT_GROUP_ID" -out=tfplan
|
||||
read -p "Apply management group changes? (yes/no): " -r
|
||||
if [[ $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
|
||||
terraform apply tfplan
|
||||
fi
|
||||
cd ..
|
||||
|
||||
# Step 2: Deploy Policies
|
||||
echo ""
|
||||
echo "Step 2: Deploying Sovereignty Policies..."
|
||||
cd policies
|
||||
terraform init
|
||||
terraform plan -var="management_group_id=$MANAGEMENT_GROUP_ID" -out=tfplan
|
||||
read -p "Apply policy changes? (yes/no): " -r
|
||||
if [[ $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
|
||||
terraform apply tfplan
|
||||
fi
|
||||
cd ..
|
||||
|
||||
# Step 3: Deploy Multi-Region Landing Zones
|
||||
echo ""
|
||||
echo "Step 3: Deploying Multi-Region Landing Zones..."
|
||||
cd multi-region
|
||||
terraform init
|
||||
terraform plan \
|
||||
-var="environment=$ENVIRONMENT" \
|
||||
-var="management_group_id=$MANAGEMENT_GROUP_ID" \
|
||||
-var="deploy_all_regions=true" \
|
||||
-out=tfplan
|
||||
|
||||
echo ""
|
||||
echo "This will deploy landing zones to:"
|
||||
echo " • West Europe (Netherlands) - Primary"
|
||||
echo " • North Europe (Ireland) - Secondary"
|
||||
echo " • UK South (London)"
|
||||
echo " • Switzerland North (Zurich)"
|
||||
echo " • Norway East (Oslo)"
|
||||
echo " • France Central (Paris)"
|
||||
echo " • Germany West Central (Frankfurt)"
|
||||
echo ""
|
||||
|
||||
read -p "Apply multi-region deployment? (yes/no): " -r
|
||||
if [[ $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
|
||||
terraform apply tfplan
|
||||
|
||||
echo ""
|
||||
echo "✅ Multi-region landing zone deployment complete!"
|
||||
echo ""
|
||||
echo "Deployment outputs:"
|
||||
terraform output
|
||||
fi
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
echo ""
|
||||
echo "╔══════════════════════════════════════════════════════════════╗"
|
||||
echo "║ DEPLOYMENT COMPLETE ║"
|
||||
echo "╚══════════════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Review deployed resources in Azure Portal"
|
||||
echo " 2. Configure application workloads"
|
||||
echo " 3. Set up monitoring and alerting"
|
||||
echo " 4. Review compliance status in Azure Policy"
|
||||
echo ""
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
# Local .terraform directories
|
||||
**/.terraform/*
|
||||
|
||||
# .tfstate files
|
||||
*.tfstate
|
||||
*.tfstate.*
|
||||
|
||||
# Crash log files
|
||||
crash.log
|
||||
crash.*.log
|
||||
|
||||
# Exclude all .tfvars files, which are likely to contain sensitive data
|
||||
*.tfvars
|
||||
*.tfvars.json
|
||||
|
||||
# Ignore override files as they are usually used to override resources locally
|
||||
override.tf
|
||||
override.tf.json
|
||||
*_override.tf
|
||||
*_override.tf.json
|
||||
|
||||
# Ignore CLI configuration files
|
||||
.terraformrc
|
||||
terraform.rc
|
||||
|
||||
# Ignore plan files
|
||||
*.tfplan
|
||||
tfplan
|
||||
|
||||
# Ignore lock files (optional - some teams prefer to commit these)
|
||||
# .terraform.lock.hcl
|
||||
|
||||
# Ignore backup files
|
||||
*.backup
|
||||
*.bak
|
||||
|
||||
# Ignore Azure CLI output files
|
||||
azure-regions.txt
|
||||
azure-quotas.txt
|
||||
azure-quotas-all-regions.txt
|
||||
|
||||
@@ -1,245 +0,0 @@
|
||||
# Azure Resource Providers - Required for The Order
|
||||
|
||||
**Last Updated**: 2025-01-27
|
||||
**Default Region**: West Europe (westeurope)
|
||||
**Policy**: No US Commercial or Government regions
|
||||
|
||||
---
|
||||
|
||||
## Required Resource Providers
|
||||
|
||||
The following Azure Resource Providers must be registered in your subscription before deploying The Order infrastructure:
|
||||
|
||||
### Core Infrastructure Providers
|
||||
|
||||
1. **Microsoft.ContainerService**
|
||||
- **Purpose**: Azure Kubernetes Service (AKS)
|
||||
- **Required For**: Kubernetes cluster deployment
|
||||
- **Registration**: Required
|
||||
|
||||
2. **Microsoft.KeyVault**
|
||||
- **Purpose**: Azure Key Vault for secrets management
|
||||
- **Required For**: Secure storage of secrets, certificates, keys
|
||||
- **Registration**: Required
|
||||
|
||||
3. **Microsoft.Storage**
|
||||
- **Purpose**: Azure Storage Accounts
|
||||
- **Required For**: Object storage, Terraform state backend
|
||||
- **Registration**: Required
|
||||
|
||||
4. **Microsoft.Network**
|
||||
- **Purpose**: Virtual Networks, Load Balancers, Application Gateway
|
||||
- **Required For**: Networking infrastructure
|
||||
- **Registration**: Required
|
||||
|
||||
5. **Microsoft.Compute**
|
||||
- **Purpose**: Virtual Machines, VM Scale Sets
|
||||
- **Required For**: AKS node pools, compute resources
|
||||
- **Registration**: Required
|
||||
|
||||
### Database & Storage Providers
|
||||
|
||||
6. **Microsoft.DBforPostgreSQL**
|
||||
- **Purpose**: Azure Database for PostgreSQL
|
||||
- **Required For**: Primary database service
|
||||
- **Registration**: Required
|
||||
|
||||
7. **Microsoft.ContainerRegistry**
|
||||
- **Purpose**: Azure Container Registry (ACR)
|
||||
- **Required For**: Container image storage and management
|
||||
- **Registration**: Required
|
||||
|
||||
### Identity & Access Providers
|
||||
|
||||
8. **Microsoft.ManagedIdentity**
|
||||
- **Purpose**: Azure Managed Identities
|
||||
- **Required For**: Service-to-service authentication without secrets
|
||||
- **Registration**: Required
|
||||
|
||||
9. **Microsoft.Authorization**
|
||||
- **Purpose**: Role-Based Access Control (RBAC)
|
||||
- **Required For**: Access control and permissions
|
||||
- **Registration**: Required
|
||||
|
||||
### Monitoring & Observability Providers
|
||||
|
||||
10. **Microsoft.Insights**
|
||||
- **Purpose**: Application Insights, Azure Monitor
|
||||
- **Required For**: Application monitoring and metrics
|
||||
- **Registration**: Required
|
||||
|
||||
11. **Microsoft.OperationalInsights**
|
||||
- **Purpose**: Log Analytics Workspaces
|
||||
- **Required For**: Centralized logging and log analysis
|
||||
- **Registration**: Required
|
||||
|
||||
### Workflow & Integration Providers
|
||||
|
||||
12. **Microsoft.Logic**
|
||||
- **Purpose**: Azure Logic Apps
|
||||
- **Required For**: Workflow orchestration (optional but recommended)
|
||||
- **Registration**: Required if using Logic Apps
|
||||
|
||||
### Resource Management Providers
|
||||
|
||||
13. **Microsoft.Resources**
|
||||
- **Purpose**: Azure Resource Manager
|
||||
- **Required For**: Resource group management, deployments
|
||||
- **Registration**: Required (usually pre-registered)
|
||||
|
||||
---
|
||||
|
||||
## Preview Features
|
||||
|
||||
Currently, no preview features are required. If Microsoft Entra VerifiedID requires preview features, they will be documented here.
|
||||
|
||||
---
|
||||
|
||||
## Registration Status
|
||||
|
||||
### Check Registration Status
|
||||
|
||||
```bash
|
||||
# Check all required providers
|
||||
./infra/scripts/azure-register-providers.sh
|
||||
|
||||
# Or check individually
|
||||
az provider show --namespace Microsoft.ContainerService
|
||||
```
|
||||
|
||||
### Register All Providers
|
||||
|
||||
```bash
|
||||
# Run the registration script
|
||||
./infra/scripts/azure-register-providers.sh
|
||||
```
|
||||
|
||||
### Manual Registration
|
||||
|
||||
If you need to register providers manually:
|
||||
|
||||
```bash
|
||||
# Register a single provider
|
||||
az provider register --namespace Microsoft.ContainerService
|
||||
|
||||
# Register all providers
|
||||
for provider in \
|
||||
Microsoft.ContainerService \
|
||||
Microsoft.KeyVault \
|
||||
Microsoft.Storage \
|
||||
Microsoft.Network \
|
||||
Microsoft.Compute \
|
||||
Microsoft.DBforPostgreSQL \
|
||||
Microsoft.ContainerRegistry \
|
||||
Microsoft.ManagedIdentity \
|
||||
Microsoft.Insights \
|
||||
Microsoft.Logic \
|
||||
Microsoft.OperationalInsights \
|
||||
Microsoft.Authorization \
|
||||
Microsoft.Resources; do
|
||||
az provider register --namespace "${provider}" --wait
|
||||
done
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Registration Verification
|
||||
|
||||
After registration, verify all providers are registered:
|
||||
|
||||
```bash
|
||||
# Check registration status
|
||||
az provider list --query "[?contains(namespace, 'Microsoft')].{Namespace:namespace, Status:registrationState}" -o table
|
||||
```
|
||||
|
||||
All providers should show `Registered` status.
|
||||
|
||||
---
|
||||
|
||||
## Regional Availability
|
||||
|
||||
**Important**: The Order uses **West Europe (westeurope)** as the default region. US Commercial and Government regions are **not used**.
|
||||
|
||||
### Recommended Regions
|
||||
|
||||
- **Primary**: `westeurope` (West Europe)
|
||||
- **Secondary**: `northeurope` (North Europe)
|
||||
- **UK**: `uksouth` (UK South)
|
||||
- **Switzerland**: `switzerlandnorth` (Switzerland North)
|
||||
- **Norway**: `norwayeast` (Norway East)
|
||||
|
||||
### Check Regional Availability
|
||||
|
||||
Some resource providers may not be available in all regions. Check availability:
|
||||
|
||||
```bash
|
||||
# Check AKS availability
|
||||
az provider show --namespace Microsoft.ContainerService --query "resourceTypes[?resourceType=='managedClusters'].locations" -o table
|
||||
|
||||
# Check PostgreSQL availability
|
||||
az provider show --namespace Microsoft.DBforPostgreSQL --query "resourceTypes[?resourceType=='servers'].locations" -o table
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Provider Registration Fails
|
||||
|
||||
1. **Check Subscription Permissions**
|
||||
```bash
|
||||
az account show
|
||||
az role assignment list --assignee $(az account show --query user.name -o tsv)
|
||||
```
|
||||
|
||||
2. **Check Subscription State**
|
||||
```bash
|
||||
az account show --query state
|
||||
```
|
||||
Must be `Enabled`
|
||||
|
||||
3. **Wait for Registration**
|
||||
- Some providers take 5-10 minutes to register
|
||||
- Use `--wait` flag or check status periodically
|
||||
|
||||
### Provider Not Available in Region
|
||||
|
||||
1. **Check Regional Availability**
|
||||
```bash
|
||||
az provider show --namespace <ProviderName> --query "resourceTypes[?resourceType=='<ResourceType>'].locations"
|
||||
```
|
||||
|
||||
2. **Use Alternative Region**
|
||||
- Consider using `northeurope` or `uksouth` as alternatives
|
||||
|
||||
### Quota Issues
|
||||
|
||||
1. **Check Quotas**
|
||||
```bash
|
||||
./infra/scripts/azure-check-quotas.sh
|
||||
```
|
||||
|
||||
2. **Request Quota Increase**
|
||||
- Go to Azure Portal → Subscriptions → Usage + quotas
|
||||
- Request increase for required resources
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
After registering all resource providers:
|
||||
|
||||
1. ✅ Run `./infra/scripts/azure-setup.sh` to complete Azure setup
|
||||
2. ✅ Check quotas: `./infra/scripts/azure-check-quotas.sh`
|
||||
3. ✅ Proceed with Terraform initialization: `terraform init`
|
||||
4. ✅ Plan infrastructure: `terraform plan`
|
||||
5. ✅ Deploy infrastructure: `terraform apply`
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [Azure Resource Provider Registration](https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/resource-providers-and-types)
|
||||
- [Azure CLI Provider Commands](https://docs.microsoft.com/en-us/cli/azure/provider)
|
||||
- [Azure Regional Availability](https://azure.microsoft.com/en-us/global-infrastructure/services/)
|
||||
|
||||
@@ -1,391 +0,0 @@
|
||||
# Azure Infrastructure - Execution Guide
|
||||
|
||||
**Last Updated**: 2025-01-27
|
||||
**Default Region**: West Europe (westeurope)
|
||||
**Policy**: No US Commercial or Government regions
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before executing Terraform, ensure you have:
|
||||
|
||||
1. ✅ **Azure CLI installed**
|
||||
```bash
|
||||
az --version
|
||||
```
|
||||
|
||||
2. ✅ **Logged into Azure**
|
||||
```bash
|
||||
az login
|
||||
az account show
|
||||
```
|
||||
|
||||
3. ✅ **Required permissions**
|
||||
- Subscription Contributor or Owner role
|
||||
- Ability to create resource groups
|
||||
- Ability to register resource providers
|
||||
|
||||
---
|
||||
|
||||
## Step-by-Step Execution
|
||||
|
||||
### Step 1: Run Azure Setup Scripts
|
||||
|
||||
Execute the setup scripts to prepare your Azure subscription:
|
||||
|
||||
```bash
|
||||
# Navigate to project root
|
||||
cd /home/intlc/projects/the_order
|
||||
|
||||
# Run complete setup (recommended)
|
||||
./infra/scripts/azure-setup.sh
|
||||
```
|
||||
|
||||
This will:
|
||||
- List all non-US Azure regions
|
||||
- Register all 13 required resource providers
|
||||
- Check quotas for primary regions
|
||||
- Generate reports
|
||||
|
||||
**Expected Output Files:**
|
||||
- `azure-regions.txt` - List of available regions
|
||||
- `azure-quotas.txt` - Quota information for primary regions
|
||||
|
||||
### Step 2: Verify Resource Provider Registration
|
||||
|
||||
```bash
|
||||
# Run provider registration script
|
||||
./infra/scripts/azure-register-providers.sh
|
||||
```
|
||||
|
||||
**Expected Output:**
|
||||
```
|
||||
✓ Microsoft.ContainerService - Registered
|
||||
✓ Microsoft.KeyVault - Registered
|
||||
✓ Microsoft.Storage - Registered
|
||||
...
|
||||
✓ All required resource providers are registered!
|
||||
```
|
||||
|
||||
If any providers are not registered, the script will register them automatically.
|
||||
|
||||
### Step 3: Review Quotas
|
||||
|
||||
```bash
|
||||
# Check quotas for all regions
|
||||
./infra/scripts/azure-check-quotas.sh
|
||||
```
|
||||
|
||||
**Review the output file:**
|
||||
```bash
|
||||
cat azure-quotas-all-regions.txt
|
||||
```
|
||||
|
||||
Ensure you have sufficient quotas for:
|
||||
- VM cores (for AKS nodes)
|
||||
- Storage accounts
|
||||
- Network resources
|
||||
|
||||
### Step 4: Initialize Terraform
|
||||
|
||||
```bash
|
||||
# Navigate to Terraform directory
|
||||
cd infra/terraform
|
||||
|
||||
# Initialize Terraform (downloads providers)
|
||||
terraform init
|
||||
```
|
||||
|
||||
**Expected Output:**
|
||||
```
|
||||
Initializing the backend...
|
||||
Initializing provider plugins...
|
||||
- Finding hashicorp/azurerm versions matching "~> 3.0"...
|
||||
- Installing hashicorp/azurerm v3.x.x...
|
||||
Terraform has been successfully initialized!
|
||||
```
|
||||
|
||||
### Step 5: Create Initial Infrastructure (State Storage)
|
||||
|
||||
Before using remote state, create the storage account locally:
|
||||
|
||||
```bash
|
||||
# Review the plan
|
||||
terraform plan -target=azurerm_resource_group.terraform_state -target=azurerm_storage_account.terraform_state -target=azurerm_storage_container.terraform_state
|
||||
|
||||
# Apply to create state storage
|
||||
terraform apply -target=azurerm_resource_group.terraform_state -target=azurerm_storage_account.terraform_state -target=azurerm_storage_container.terraform_state
|
||||
```
|
||||
|
||||
**Note**: This creates the storage account needed for remote state backend.
|
||||
|
||||
### Step 6: Configure Remote State Backend
|
||||
|
||||
After the storage account is created:
|
||||
|
||||
1. **Get the storage account name:**
|
||||
```bash
|
||||
terraform output -raw storage_account_name
|
||||
# Or check the Terraform state
|
||||
terraform show | grep storage_account_name
|
||||
```
|
||||
|
||||
2. **Update `versions.tf`** - Uncomment and configure the backend block:
|
||||
```hcl
|
||||
backend "azurerm" {
|
||||
resource_group_name = "the-order-terraform-state-rg"
|
||||
storage_account_name = "<output-from-above>"
|
||||
container_name = "terraform-state"
|
||||
key = "terraform.tfstate"
|
||||
}
|
||||
```
|
||||
|
||||
3. **Re-initialize with backend:**
|
||||
```bash
|
||||
terraform init -migrate-state
|
||||
```
|
||||
|
||||
### Step 7: Plan Full Infrastructure
|
||||
|
||||
```bash
|
||||
# Review what will be created
|
||||
terraform plan
|
||||
|
||||
# Save plan to file for review
|
||||
terraform plan -out=tfplan
|
||||
```
|
||||
|
||||
**Review the plan carefully** to ensure:
|
||||
- Correct resource names
|
||||
- Correct region (should be `westeurope`)
|
||||
- No US regions are being used
|
||||
- Appropriate resource sizes
|
||||
|
||||
### Step 8: Apply Infrastructure
|
||||
|
||||
```bash
|
||||
# Apply the plan
|
||||
terraform apply
|
||||
|
||||
# Or use the saved plan
|
||||
terraform apply tfplan
|
||||
```
|
||||
|
||||
**Expected Resources Created:**
|
||||
- Resource groups
|
||||
- Storage accounts
|
||||
- (Additional resources as you add them)
|
||||
|
||||
### Step 9: Verify Deployment
|
||||
|
||||
```bash
|
||||
# List created resources
|
||||
az resource list --resource-group the-order-dev-rg --output table
|
||||
|
||||
# Check resource group
|
||||
az group show --name the-order-dev-rg
|
||||
|
||||
# Verify region
|
||||
az group show --name the-order-dev-rg --query location
|
||||
# Should output: "westeurope"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Environment-Specific Deployment
|
||||
|
||||
### Development Environment
|
||||
|
||||
```bash
|
||||
# Set environment variable
|
||||
export TF_VAR_environment=dev
|
||||
|
||||
# Or use -var flag
|
||||
terraform plan -var="environment=dev"
|
||||
terraform apply -var="environment=dev"
|
||||
```
|
||||
|
||||
### Staging Environment
|
||||
|
||||
```bash
|
||||
terraform plan -var="environment=stage"
|
||||
terraform apply -var="environment=stage"
|
||||
```
|
||||
|
||||
### Production Environment
|
||||
|
||||
```bash
|
||||
# Production requires extra caution
|
||||
terraform plan -var="environment=prod" -detailed-exitcode
|
||||
terraform apply -var="environment=prod"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Error: Resource Provider Not Registered
|
||||
|
||||
**Symptom:**
|
||||
```
|
||||
Error: creating Resource Group: resources.ResourcesClient#CreateOrUpdate:
|
||||
Failure sending request: StatusCode=400 -- Original Error:
|
||||
Code="MissingSubscriptionRegistration"
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Register the provider
|
||||
az provider register --namespace Microsoft.Resources --wait
|
||||
|
||||
# Or run the registration script
|
||||
./infra/scripts/azure-register-providers.sh
|
||||
```
|
||||
|
||||
### Error: Quota Exceeded
|
||||
|
||||
**Symptom:**
|
||||
```
|
||||
Error: creating Storage Account: storage.AccountsClient#Create:
|
||||
Failure sending request: StatusCode=400 -- Original Error:
|
||||
Code="SubscriptionQuotaExceeded"
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
1. Check quotas: `./infra/scripts/azure-check-quotas.sh`
|
||||
2. Request quota increase in Azure Portal
|
||||
3. Or use a different region
|
||||
|
||||
### Error: Invalid Region
|
||||
|
||||
**Symptom:**
|
||||
```
|
||||
Error: invalid location "us-east-1"
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
- Ensure you're using `westeurope` or another non-US region
|
||||
- Check `variables.tf` - default should be `westeurope`
|
||||
- Terraform validation should prevent US regions
|
||||
|
||||
### Error: Storage Account Name Already Exists
|
||||
|
||||
**Symptom:**
|
||||
```
|
||||
Error: creating Storage Account: storage.AccountsClient#Create:
|
||||
Failure sending request: StatusCode=409 -- Original Error:
|
||||
Code="StorageAccountAlreadyTaken"
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
- Storage account names must be globally unique
|
||||
- Modify the name in `storage.tf` or use a different project name
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Always Review Plans
|
||||
|
||||
```bash
|
||||
# Always review before applying
|
||||
terraform plan -out=tfplan
|
||||
terraform show tfplan
|
||||
```
|
||||
|
||||
### 2. Use Workspaces for Multiple Environments
|
||||
|
||||
```bash
|
||||
# Create workspace for dev
|
||||
terraform workspace new dev
|
||||
|
||||
# Create workspace for prod
|
||||
terraform workspace new prod
|
||||
|
||||
# Switch between workspaces
|
||||
terraform workspace select dev
|
||||
```
|
||||
|
||||
### 3. Version Control
|
||||
|
||||
- ✅ Commit Terraform files to version control
|
||||
- ❌ Never commit `.tfstate` files
|
||||
- ✅ Use remote state backend (Azure Storage)
|
||||
- ✅ Use `.tfvars` files for environment-specific values (add to `.gitignore`)
|
||||
|
||||
### 4. State Management
|
||||
|
||||
- ✅ Use remote state backend
|
||||
- ✅ Enable state locking (automatic with Azure Storage)
|
||||
- ✅ Enable versioning on storage account
|
||||
- ✅ Regular backups of state
|
||||
|
||||
### 5. Security
|
||||
|
||||
- ✅ Use Azure Key Vault for secrets
|
||||
- ✅ Use Managed Identities where possible
|
||||
- ✅ Enable soft delete on Key Vault
|
||||
- ✅ Enable versioning on storage accounts
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
After initial infrastructure is created:
|
||||
|
||||
1. **Create Azure Key Vault**
|
||||
- For secrets management
|
||||
- See `key-vault.tf` (to be created)
|
||||
|
||||
2. **Create AKS Cluster**
|
||||
- For Kubernetes deployment
|
||||
- See `aks.tf` (to be created)
|
||||
|
||||
3. **Create PostgreSQL Database**
|
||||
- For application database
|
||||
- See `database.tf` (to be created)
|
||||
|
||||
4. **Create Container Registry**
|
||||
- For container images
|
||||
- See `container-registry.tf` (to be created)
|
||||
|
||||
5. **Configure Networking**
|
||||
- Virtual networks, subnets, NSGs
|
||||
- See `network.tf` (to be created)
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference Commands
|
||||
|
||||
```bash
|
||||
# Setup
|
||||
./infra/scripts/azure-setup.sh
|
||||
./infra/scripts/azure-register-providers.sh
|
||||
|
||||
# Terraform
|
||||
cd infra/terraform
|
||||
terraform init
|
||||
terraform plan
|
||||
terraform apply
|
||||
terraform destroy
|
||||
|
||||
# Verification
|
||||
az resource list --resource-group the-order-dev-rg
|
||||
az group show --name the-order-dev-rg
|
||||
terraform output
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
- **Resource Providers**: See `AZURE_RESOURCE_PROVIDERS.md`
|
||||
- **Scripts**: See `infra/scripts/README.md`
|
||||
- **Troubleshooting**: See sections above
|
||||
- **Azure CLI Docs**: https://docs.microsoft.com/en-us/cli/azure/
|
||||
|
||||
---
|
||||
|
||||
**Ready to deploy!** 🚀
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
# Naming Validation
|
||||
|
||||
This document provides validation rules and examples for the naming convention.
|
||||
|
||||
## Validation Rules
|
||||
|
||||
### Resource Group Names
|
||||
- **Pattern**: `az-{region}-rg-{env}-{purpose}`
|
||||
- **Example**: `az-we-rg-dev-main`
|
||||
- **Validation**: `^az-[a-z]{2}-rg-(dev|stg|prd|mgmt)-[a-z]{3,15}$`
|
||||
|
||||
### Storage Account Names
|
||||
- **Pattern**: `az{region}sa{env}{purpose}`
|
||||
- **Example**: `azwesadevdata`
|
||||
- **Max Length**: 24 characters
|
||||
- **Validation**: `^az[a-z]{2}sa(dev|stg|prd|mgmt)[a-z]{3,10}$`
|
||||
|
||||
### Key Vault Names
|
||||
- **Pattern**: `az-{region}-kv-{env}-{purpose}`
|
||||
- **Example**: `az-we-kv-dev-main`
|
||||
- **Max Length**: 24 characters
|
||||
- **Validation**: `^az-[a-z]{2}-kv-(dev|stg|prd|mgmt)-[a-z]{3,10}$`
|
||||
|
||||
### AKS Cluster Names
|
||||
- **Pattern**: `az-{region}-aks-{env}-{purpose}`
|
||||
- **Example**: `az-we-aks-dev-main`
|
||||
- **Max Length**: 63 characters
|
||||
- **Validation**: `^az-[a-z]{2}-aks-(dev|stg|prd|mgmt)-[a-z]{3,15}$`
|
||||
|
||||
### Container Registry Names
|
||||
- **Pattern**: `az{region}acr{env}`
|
||||
- **Example**: `azweacrdev`
|
||||
- **Max Length**: 50 characters
|
||||
- **Validation**: `^az[a-z]{2}acr(dev|stg|prd|mgmt)$`
|
||||
|
||||
## Testing
|
||||
|
||||
Run Terraform validation:
|
||||
|
||||
```bash
|
||||
cd infra/terraform
|
||||
terraform validate
|
||||
terraform plan
|
||||
```
|
||||
|
||||
Check name lengths:
|
||||
|
||||
```bash
|
||||
# Storage accounts must be <= 24 chars
|
||||
echo "azwesadevdata" | wc -c # Should be <= 24
|
||||
|
||||
# Key Vaults must be <= 24 chars
|
||||
echo "az-we-kv-dev-main" | wc -c # Should be <= 24
|
||||
```
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
# Terraform Infrastructure
|
||||
|
||||
Terraform configuration for The Order infrastructure on Azure.
|
||||
|
||||
**Default Region**: West Europe (westeurope)
|
||||
**Policy**: No US Commercial or Government regions
|
||||
|
||||
## Structure
|
||||
|
||||
- `versions.tf` - Terraform and provider version constraints
|
||||
- `main.tf` - Azure provider configuration
|
||||
- `variables.tf` - Variable definitions
|
||||
- `outputs.tf` - Output definitions
|
||||
- `resource-groups.tf` - Resource group definitions
|
||||
- `storage.tf` - Storage account definitions
|
||||
- `modules/` - Reusable Terraform modules (to be created)
|
||||
- `AZURE_RESOURCE_PROVIDERS.md` - Required resource providers documentation
|
||||
- `EXECUTION_GUIDE.md` - Step-by-step execution guide
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before using Terraform:
|
||||
|
||||
1. **Run Azure setup scripts** (from project root):
|
||||
```bash
|
||||
./infra/scripts/azure-setup.sh
|
||||
./infra/scripts/azure-register-providers.sh
|
||||
```
|
||||
|
||||
2. **Verify Azure CLI is installed and logged in**:
|
||||
```bash
|
||||
az --version
|
||||
az account show
|
||||
```
|
||||
|
||||
3. **Ensure required resource providers are registered**:
|
||||
See `AZURE_RESOURCE_PROVIDERS.md` for complete list.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Navigate to Terraform directory
|
||||
cd infra/terraform
|
||||
|
||||
# Initialize Terraform
|
||||
terraform init
|
||||
|
||||
# Review what will be created
|
||||
terraform plan
|
||||
|
||||
# Apply changes
|
||||
terraform apply
|
||||
```
|
||||
|
||||
## Detailed Execution
|
||||
|
||||
See `EXECUTION_GUIDE.md` for comprehensive step-by-step instructions.
|
||||
|
||||
## Environments
|
||||
|
||||
Environments are managed via the `environment` variable:
|
||||
|
||||
- `dev` - Development environment
|
||||
- `stage` - Staging environment
|
||||
- `prod` - Production environment
|
||||
|
||||
```bash
|
||||
# Deploy to specific environment
|
||||
terraform plan -var="environment=dev"
|
||||
terraform apply -var="environment=dev"
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
### Currently Defined
|
||||
|
||||
- ✅ Resource Groups
|
||||
- ✅ Storage Accounts (application data and Terraform state)
|
||||
- ✅ Storage Containers
|
||||
|
||||
### To Be Created
|
||||
|
||||
- ⏳ Azure Kubernetes Service (AKS) cluster
|
||||
- ⏳ Azure Database for PostgreSQL
|
||||
- ⏳ Azure Key Vault
|
||||
- ⏳ Azure Container Registry (ACR)
|
||||
- ⏳ Virtual Networks and Subnets
|
||||
- ⏳ Application Gateway / Load Balancer
|
||||
- ⏳ Azure Monitor and Log Analytics
|
||||
|
||||
## Configuration
|
||||
|
||||
### Default Region
|
||||
|
||||
Default region is **West Europe (westeurope)**. US regions are not allowed.
|
||||
|
||||
To use a different region:
|
||||
```bash
|
||||
terraform plan -var="azure_region=northeurope"
|
||||
```
|
||||
|
||||
### Variables
|
||||
|
||||
Key variables (see `variables.tf` for complete list):
|
||||
|
||||
- `azure_region` - Azure region (default: `westeurope`)
|
||||
- `environment` - Environment name (`dev`, `stage`, `prod`)
|
||||
- `project_name` - Project name (default: `the-order`)
|
||||
- `create_terraform_state_storage` - Create state storage (default: `true`)
|
||||
|
||||
## Secrets Management
|
||||
|
||||
Secrets are managed using:
|
||||
- Azure Key Vault (to be configured)
|
||||
- External Secrets Operator for Kubernetes (to be configured)
|
||||
- SOPS for local development (optional)
|
||||
|
||||
## State Management
|
||||
|
||||
Terraform state is stored in Azure Storage Account:
|
||||
|
||||
1. First deployment creates storage account locally
|
||||
2. After creation, configure remote backend in `versions.tf`
|
||||
3. Re-initialize with `terraform init -migrate-state`
|
||||
|
||||
See `EXECUTION_GUIDE.md` for detailed instructions.
|
||||
|
||||
## Outputs
|
||||
|
||||
Key outputs (see `outputs.tf` for complete list):
|
||||
|
||||
- `resource_group_name` - Main resource group name
|
||||
- `storage_account_name` - Application data storage account
|
||||
- `azure_region` - Azure region being used
|
||||
|
||||
View outputs:
|
||||
```bash
|
||||
terraform output
|
||||
terraform output resource_group_name
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. ✅ Always review `terraform plan` before applying
|
||||
2. ✅ Use workspaces for multiple environments
|
||||
3. ✅ Never commit `.tfstate` files
|
||||
4. ✅ Use remote state backend
|
||||
5. ✅ Enable versioning on storage accounts
|
||||
6. ✅ Use `.tfvars` files for environment-specific values
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Common issues and solutions:
|
||||
|
||||
### Resource Provider Not Registered
|
||||
```bash
|
||||
./infra/scripts/azure-register-providers.sh
|
||||
```
|
||||
|
||||
### Quota Exceeded
|
||||
```bash
|
||||
./infra/scripts/azure-check-quotas.sh
|
||||
# Request quota increase in Azure Portal
|
||||
```
|
||||
|
||||
### Invalid Region
|
||||
- Ensure region doesn't start with `us`
|
||||
- Default is `westeurope`
|
||||
- See validation in `variables.tf`
|
||||
|
||||
See `EXECUTION_GUIDE.md` for more troubleshooting tips.
|
||||
|
||||
## Documentation
|
||||
|
||||
- **Execution Guide**: `EXECUTION_GUIDE.md` - Step-by-step deployment instructions
|
||||
- **Resource Providers**: `AZURE_RESOURCE_PROVIDERS.md` - Required providers and registration
|
||||
- **Setup Scripts**: `../scripts/README.md` - Azure CLI setup scripts
|
||||
- **Deployment Review**: `../../docs/reports/DEPLOYMENT_READINESS_REVIEW.md` - Overall deployment status
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. ✅ Run setup scripts to register providers
|
||||
2. ✅ Initialize Terraform
|
||||
3. ✅ Create initial infrastructure (resource groups, storage)
|
||||
4. ⏳ Configure remote state backend
|
||||
5. ⏳ Add additional resources (AKS, PostgreSQL, Key Vault, etc.)
|
||||
|
||||
---
|
||||
|
||||
**See `EXECUTION_GUIDE.md` for detailed step-by-step instructions.**
|
||||
@@ -1,101 +0,0 @@
|
||||
# Azure Kubernetes Service (AKS) Configuration
|
||||
|
||||
variable "aks_cluster_name" {
|
||||
description = "Name of the AKS cluster"
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "aks_node_count" {
|
||||
description = "Number of nodes in the AKS cluster"
|
||||
type = number
|
||||
default = 2
|
||||
}
|
||||
|
||||
variable "aks_vm_size" {
|
||||
description = "VM size for AKS nodes"
|
||||
type = string
|
||||
default = "Standard_B2s"
|
||||
}
|
||||
|
||||
resource "azurerm_kubernetes_cluster" "main" {
|
||||
name = var.aks_cluster_name != "" ? var.aks_cluster_name : "the-order-aks-${var.environment}"
|
||||
location = var.azure_region
|
||||
resource_group_name = azurerm_resource_group.main.name
|
||||
dns_prefix = "the-order-${var.environment}"
|
||||
kubernetes_version = "1.28" # Update to latest stable
|
||||
|
||||
# Use subscription_id from variable if provided
|
||||
# This ensures proper Azure authentication
|
||||
|
||||
default_node_pool {
|
||||
name = "default"
|
||||
node_count = var.aks_node_count
|
||||
vm_size = var.aks_vm_size
|
||||
type = "VirtualMachineScaleSets"
|
||||
enable_auto_scaling = var.environment != "dev"
|
||||
min_count = var.environment != "dev" ? 2 : null
|
||||
max_count = var.environment != "dev" ? 10 : null
|
||||
os_disk_size_gb = 30
|
||||
}
|
||||
|
||||
identity {
|
||||
type = "SystemAssigned"
|
||||
}
|
||||
|
||||
# Enable Azure RBAC
|
||||
azure_active_directory_role_based_access_control {
|
||||
managed = true
|
||||
azure_rbac_enabled = true
|
||||
admin_group_object_ids = [] # Add admin group IDs
|
||||
}
|
||||
|
||||
# Network profile
|
||||
network_profile {
|
||||
network_plugin = "azure"
|
||||
network_policy = "azure"
|
||||
load_balancer_sku = "standard"
|
||||
}
|
||||
|
||||
# Enable monitoring
|
||||
oms_agent {
|
||||
log_analytics_workspace_id = azurerm_log_analytics_workspace.main[0].id
|
||||
}
|
||||
|
||||
tags = var.tags
|
||||
}
|
||||
|
||||
# Log Analytics Workspace for AKS monitoring
|
||||
resource "azurerm_log_analytics_workspace" "main" {
|
||||
count = var.create_aks_cluster ? 1 : 0
|
||||
name = "the-order-logs-${var.environment}"
|
||||
location = var.azure_region
|
||||
resource_group_name = azurerm_resource_group.main.name
|
||||
sku = "PerGB2018"
|
||||
retention_in_days = var.environment == "prod" ? 90 : 30
|
||||
|
||||
tags = var.tags
|
||||
}
|
||||
|
||||
# Output AKS details
|
||||
output "aks_cluster_name" {
|
||||
value = var.create_aks_cluster ? azurerm_kubernetes_cluster.main[0].name : null
|
||||
description = "Name of the AKS cluster"
|
||||
}
|
||||
|
||||
output "aks_fqdn" {
|
||||
value = var.create_aks_cluster ? azurerm_kubernetes_cluster.main[0].fqdn : null
|
||||
description = "FQDN of the AKS cluster"
|
||||
}
|
||||
|
||||
output "aks_kube_config" {
|
||||
value = var.create_aks_cluster ? azurerm_kubernetes_cluster.main[0].kube_config_raw : null
|
||||
description = "Raw Kubernetes config"
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "log_analytics_workspace_id" {
|
||||
value = azurerm_log_analytics_workspace.main[0].workspace_id
|
||||
description = "Log Analytics Workspace ID"
|
||||
}
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
# Azure Provider Configuration
|
||||
# Uses environment variables or terraform.tfvars for authentication
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
|
||||
required_providers {
|
||||
azurerm = {
|
||||
source = "hashicorp/azurerm"
|
||||
version = "~> 3.0"
|
||||
}
|
||||
}
|
||||
|
||||
# Backend configuration (uncomment and configure for remote state)
|
||||
# backend "azurerm" {
|
||||
# resource_group_name = "the-order-tfstate-rg"
|
||||
# storage_account_name = "theordertfstate"
|
||||
# container_name = "tfstate"
|
||||
# key = "terraform.tfstate"
|
||||
# }
|
||||
}
|
||||
|
||||
# Configure the Azure Provider
|
||||
provider "azurerm" {
|
||||
features {
|
||||
resource_group {
|
||||
prevent_deletion_if_contains_resources = false
|
||||
}
|
||||
key_vault {
|
||||
purge_soft_delete_on_destroy = true
|
||||
}
|
||||
storage {
|
||||
purge_soft_delete_on_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
# Use environment variables or terraform.tfvars
|
||||
# subscription_id = var.subscription_id
|
||||
# tenant_id = var.tenant_id
|
||||
# client_id = var.client_id
|
||||
# client_secret = var.client_secret
|
||||
}
|
||||
|
||||
# Data source for current subscription
|
||||
data "azurerm_subscription" "current" {}
|
||||
|
||||
# Data source for current client config
|
||||
data "azurerm_client_config" "current" {}
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
# Azure CDN Infrastructure for Credential Seal Images
|
||||
# Creates storage account, container, and CDN profile/endpoint
|
||||
|
||||
# Storage Account for CDN Images
|
||||
resource "azurerm_storage_account" "cdn_images" {
|
||||
name = local.sa_cdn_name
|
||||
resource_group_name = azurerm_resource_group.main.name
|
||||
location = var.azure_region
|
||||
account_tier = "Standard"
|
||||
account_replication_type = "LRS"
|
||||
min_tls_version = "TLS1_2"
|
||||
allow_blob_public_access = true
|
||||
|
||||
# Enable blob versioning for image protection
|
||||
blob_properties {
|
||||
versioning_enabled = true
|
||||
delete_retention_policy {
|
||||
days = var.environment == "prod" ? 90 : 30
|
||||
}
|
||||
cors_rule {
|
||||
allowed_origins = ["*"]
|
||||
allowed_methods = ["GET", "HEAD", "OPTIONS"]
|
||||
allowed_headers = ["*"]
|
||||
exposed_headers = ["*"]
|
||||
max_age_in_seconds = 3600
|
||||
}
|
||||
}
|
||||
|
||||
tags = merge(var.tags, {
|
||||
Purpose = "CDNImages"
|
||||
})
|
||||
}
|
||||
|
||||
# Storage Container for Images
|
||||
resource "azurerm_storage_container" "cdn_images" {
|
||||
name = "images"
|
||||
storage_account_name = azurerm_storage_account.cdn_images.name
|
||||
container_access_type = "blob"
|
||||
}
|
||||
|
||||
# CDN Profile
|
||||
resource "azurerm_cdn_profile" "cdn_images" {
|
||||
name = var.cdn_profile_name != "" ? var.cdn_profile_name : "theorder-cdn-${var.environment}"
|
||||
location = var.azure_region
|
||||
resource_group_name = azurerm_resource_group.main.name
|
||||
sku = "Standard_Microsoft"
|
||||
|
||||
tags = merge(var.tags, {
|
||||
Purpose = "CDNProfile"
|
||||
})
|
||||
}
|
||||
|
||||
# CDN Endpoint
|
||||
resource "azurerm_cdn_endpoint" "cdn_images" {
|
||||
name = var.cdn_endpoint_name != "" ? var.cdn_endpoint_name : "theorder-cdn-endpoint-${var.environment}"
|
||||
profile_name = azurerm_cdn_profile.cdn_images.name
|
||||
location = var.azure_region
|
||||
resource_group_name = azurerm_resource_group.main.name
|
||||
|
||||
origin {
|
||||
name = "blob-origin"
|
||||
host_name = azurerm_storage_account.cdn_images.primary_blob_host
|
||||
}
|
||||
|
||||
# Enable compression
|
||||
is_compression_enabled = true
|
||||
compression_types = ["gzip", "deflate"]
|
||||
|
||||
# Global delivery rule for cache
|
||||
global_delivery_rule {
|
||||
cache_expiration_action {
|
||||
behavior = "Override"
|
||||
duration = "1.00:00:00" # 1 day
|
||||
}
|
||||
}
|
||||
|
||||
tags = merge(var.tags, {
|
||||
Purpose = "CDNEndpoint"
|
||||
})
|
||||
}
|
||||
|
||||
# Outputs
|
||||
output "cdn_storage_account_name" {
|
||||
description = "CDN storage account name"
|
||||
value = azurerm_storage_account.cdn_images.name
|
||||
}
|
||||
|
||||
output "cdn_storage_account_key" {
|
||||
description = "CDN storage account primary key"
|
||||
value = azurerm_storage_account.cdn_images.primary_access_key
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "cdn_container_name" {
|
||||
description = "CDN container name"
|
||||
value = azurerm_storage_container.cdn_images.name
|
||||
}
|
||||
|
||||
output "cdn_blob_url" {
|
||||
description = "CDN blob storage URL"
|
||||
value = "https://${azurerm_storage_account.cdn_images.name}.blob.core.windows.net/${azurerm_storage_container.cdn_images.name}/"
|
||||
}
|
||||
|
||||
output "cdn_endpoint_url" {
|
||||
description = "CDN endpoint URL"
|
||||
value = "https://${azurerm_cdn_endpoint.cdn_images.host_name}/${azurerm_storage_container.cdn_images.name}/"
|
||||
}
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
# Azure Database for PostgreSQL
|
||||
# Flexible Server for production workloads
|
||||
|
||||
variable "database_name" {
|
||||
description = "PostgreSQL database name"
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "database_admin_user" {
|
||||
description = "PostgreSQL admin username"
|
||||
type = string
|
||||
default = "theorder_admin"
|
||||
}
|
||||
|
||||
variable "database_sku_name" {
|
||||
description = "PostgreSQL SKU (e.g., Standard_B1ms, Standard_B2s)"
|
||||
type = string
|
||||
default = "Standard_B1ms"
|
||||
}
|
||||
|
||||
variable "database_storage_mb" {
|
||||
description = "PostgreSQL storage in MB"
|
||||
type = number
|
||||
default = 32768 # 32 GB
|
||||
}
|
||||
|
||||
resource "azurerm_postgresql_flexible_server" "main" {
|
||||
name = var.database_name != "" ? var.database_name : "the-order-db-${var.environment}"
|
||||
resource_group_name = azurerm_resource_group.main.name
|
||||
location = var.azure_region
|
||||
version = "15"
|
||||
delegated_subnet_id = null # Set if using VNet integration
|
||||
private_dns_zone_id = null # Set if using private DNS
|
||||
administrator_login = var.database_admin_user
|
||||
administrator_password = null # Set via Key Vault secret
|
||||
zone = "1"
|
||||
|
||||
storage_mb = var.database_storage_mb
|
||||
sku_name = var.database_sku_name
|
||||
|
||||
backup {
|
||||
geo_redundant_backup_enabled = var.environment == "prod"
|
||||
backup_retention_days = var.environment == "prod" ? 35 : 7
|
||||
}
|
||||
|
||||
high_availability {
|
||||
mode = var.environment == "prod" ? "ZoneRedundant" : "Disabled"
|
||||
standby_availability_zone = var.environment == "prod" ? "2" : null
|
||||
}
|
||||
|
||||
maintenance_window {
|
||||
day_of_week = 0 # Sunday
|
||||
start_hour = 2
|
||||
start_minute = 0
|
||||
}
|
||||
|
||||
tags = merge(var.tags, {
|
||||
Purpose = "Database"
|
||||
})
|
||||
}
|
||||
|
||||
# Database
|
||||
resource "azurerm_postgresql_flexible_server_database" "main" {
|
||||
count = var.create_database ? 1 : 0
|
||||
name = "theorder_${var.environment}"
|
||||
server_id = azurerm_postgresql_flexible_server.main[0].id
|
||||
charset = "UTF8"
|
||||
collation = "en_US.utf8"
|
||||
}
|
||||
|
||||
# Firewall rules - allow Azure services
|
||||
resource "azurerm_postgresql_flexible_server_firewall_rule" "azure_services" {
|
||||
count = var.create_database ? 1 : 0
|
||||
name = "AllowAzureServices"
|
||||
server_id = azurerm_postgresql_flexible_server.main[0].id
|
||||
start_ip_address = "0.0.0.0"
|
||||
end_ip_address = "0.0.0.0"
|
||||
}
|
||||
|
||||
# Generate random password for database
|
||||
resource "random_password" "database_password" {
|
||||
count = var.create_database ? 1 : 0
|
||||
length = 32
|
||||
special = true
|
||||
}
|
||||
|
||||
# Store database connection string in Key Vault
|
||||
resource "azurerm_key_vault_secret" "database_url" {
|
||||
count = var.create_database ? 1 : 0
|
||||
name = "database-url"
|
||||
value = "postgresql://${var.database_admin_user}:${random_password.database_password[0].result}@${azurerm_postgresql_flexible_server.main[0].fqdn}:5432/${azurerm_postgresql_flexible_server_database.main[0].name}?sslmode=require"
|
||||
key_vault_id = azurerm_key_vault.main.id
|
||||
|
||||
tags = var.tags
|
||||
}
|
||||
|
||||
# Store password in Key Vault
|
||||
resource "azurerm_key_vault_secret" "database_password" {
|
||||
count = var.create_database ? 1 : 0
|
||||
name = "database-password"
|
||||
value = random_password.database_password[0].result
|
||||
key_vault_id = azurerm_key_vault.main.id
|
||||
|
||||
tags = var.tags
|
||||
}
|
||||
|
||||
# Outputs
|
||||
output "database_fqdn" {
|
||||
value = var.create_database ? azurerm_postgresql_flexible_server.main[0].fqdn : null
|
||||
description = "Fully qualified domain name of the database server"
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "database_name" {
|
||||
value = var.create_database ? azurerm_postgresql_flexible_server_database.main[0].name : null
|
||||
description = "Name of the database"
|
||||
}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
# Azure Key Vault for secrets management
|
||||
|
||||
resource "azurerm_key_vault" "main" {
|
||||
name = var.key_vault_name != "" ? var.key_vault_name : "the-order-kv-${var.environment}"
|
||||
location = var.azure_region
|
||||
resource_group_name = azurerm_resource_group.main.name
|
||||
tenant_id = var.tenant_id != "" ? var.tenant_id : data.azurerm_client_config.current.tenant_id
|
||||
|
||||
sku_name = "standard"
|
||||
|
||||
# Network ACLs
|
||||
network_acls {
|
||||
default_action = "Deny"
|
||||
bypass = "AzureServices"
|
||||
ip_rules = [] # Add allowed IPs for access
|
||||
}
|
||||
|
||||
# Enable soft delete and purge protection
|
||||
soft_delete_retention_days = 7
|
||||
purge_protection_enabled = var.environment == "prod"
|
||||
|
||||
tags = merge(var.tags, {
|
||||
Purpose = "SecretsManagement"
|
||||
})
|
||||
}
|
||||
|
||||
# Grant current user/service principal access
|
||||
resource "azurerm_key_vault_access_policy" "current_user" {
|
||||
key_vault_id = azurerm_key_vault.main.id
|
||||
tenant_id = data.azurerm_client_config.current.tenant_id
|
||||
object_id = data.azurerm_client_config.current.object_id
|
||||
|
||||
key_permissions = [
|
||||
"Get", "List", "Create", "Delete", "Update", "Import", "Backup", "Restore"
|
||||
]
|
||||
|
||||
secret_permissions = [
|
||||
"Get", "List", "Set", "Delete", "Recover", "Backup", "Restore", "Purge"
|
||||
]
|
||||
|
||||
certificate_permissions = [
|
||||
"Get", "List", "Create", "Delete", "Update", "Import", "Backup", "Restore"
|
||||
]
|
||||
}
|
||||
|
||||
# Output Key Vault details
|
||||
output "key_vault_name" {
|
||||
value = azurerm_key_vault.main.name
|
||||
description = "Name of the Key Vault"
|
||||
}
|
||||
|
||||
output "key_vault_uri" {
|
||||
value = azurerm_key_vault.main.vault_uri
|
||||
description = "URI of the Key Vault"
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
# Local values for naming conventions and common configurations
|
||||
# Follows standard naming pattern: {provider}-{region}-{resource}-{env}-{purpose}
|
||||
|
||||
locals {
|
||||
# Provider identifier
|
||||
provider = "az"
|
||||
|
||||
# Region abbreviation mapping
|
||||
region_abbrev = {
|
||||
westeurope = "we"
|
||||
northeurope = "ne"
|
||||
uksouth = "uk"
|
||||
switzerlandnorth = "ch"
|
||||
norwayeast = "no"
|
||||
francecentral = "fr"
|
||||
germanywestcentral = "de"
|
||||
}
|
||||
|
||||
# Current region abbreviation
|
||||
region_short = lookup(local.region_abbrev, var.azure_region, "we")
|
||||
|
||||
# Environment abbreviations
|
||||
env_abbrev = {
|
||||
dev = "dev"
|
||||
stage = "stg"
|
||||
prod = "prd"
|
||||
mgmt = "mgmt"
|
||||
}
|
||||
|
||||
# Current environment abbreviation
|
||||
env_short = lookup(local.env_abbrev, var.environment, "dev")
|
||||
|
||||
# Project name (shortened for resource names)
|
||||
project_short = "ord"
|
||||
|
||||
# Naming functions
|
||||
# Format: {provider}-{region}-{resource}-{env}-{purpose}
|
||||
name_prefix = "${local.provider}-${local.region_short}"
|
||||
|
||||
# Resource Group naming
|
||||
# Pattern: az-we-rg-dev-main
|
||||
rg_name = "${local.name_prefix}-rg-${local.env_short}-main"
|
||||
rg_state_name = "${local.name_prefix}-rg-${local.env_short}-state"
|
||||
|
||||
# Storage Account naming (alphanumeric only, max 24 chars)
|
||||
# Pattern: azwesadevdata (az + we + sa + dev + data)
|
||||
sa_data_name = "${local.provider}${local.region_short}sa${local.env_short}data"
|
||||
sa_state_name = "${local.provider}${local.region_short}sa${local.env_short}state"
|
||||
sa_cdn_name = "${local.provider}${local.region_short}sa${local.env_short}cdn"
|
||||
|
||||
# Key Vault naming (alphanumeric and hyphens, max 24 chars)
|
||||
# Pattern: az-we-kv-dev-main
|
||||
kv_name = "${local.name_prefix}-kv-${local.env_short}-main"
|
||||
|
||||
# AKS Cluster naming (max 63 chars)
|
||||
# Pattern: az-we-aks-dev-main
|
||||
aks_name = "${local.name_prefix}-aks-${local.env_short}-main"
|
||||
|
||||
# Container Registry naming (alphanumeric only, max 50 chars)
|
||||
# Pattern: azweacrdev (az + we + acr + dev)
|
||||
acr_name = "${local.provider}${local.region_short}acr${local.env_short}"
|
||||
|
||||
# PostgreSQL Server naming (max 63 chars)
|
||||
# Pattern: az-we-psql-dev-main
|
||||
psql_name = "${local.name_prefix}-psql-${local.env_short}-main"
|
||||
|
||||
# Database naming (max 63 chars)
|
||||
# Pattern: az-we-db-dev-main
|
||||
db_name = "${local.name_prefix}-db-${local.env_short}-main"
|
||||
|
||||
# Virtual Network naming (max 64 chars)
|
||||
# Pattern: az-we-vnet-dev-main
|
||||
vnet_name = "${local.name_prefix}-vnet-${local.env_short}-main"
|
||||
|
||||
# Application Insights naming (max 255 chars)
|
||||
# Pattern: az-we-appi-dev-main
|
||||
appi_name = "${local.name_prefix}-appi-${local.env_short}-main"
|
||||
|
||||
# Log Analytics Workspace naming (max 63 chars)
|
||||
# Pattern: az-we-law-dev-main
|
||||
law_name = "${local.name_prefix}-law-${local.env_short}-main"
|
||||
|
||||
# Common tags
|
||||
common_tags = {
|
||||
Environment = var.environment
|
||||
Project = var.project_name
|
||||
Region = var.azure_region
|
||||
ManagedBy = "Terraform"
|
||||
CostCenter = "engineering"
|
||||
Owner = "platform-team"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
# Terraform configuration for The Order infrastructure
|
||||
# Azure provider configuration - No US Commercial or Government regions
|
||||
# Version constraints are in versions.tf
|
||||
|
||||
# Configure the Azure Provider
|
||||
provider "azurerm" {
|
||||
features {
|
||||
resource_group {
|
||||
prevent_deletion_if_contains_resources = false
|
||||
}
|
||||
key_vault {
|
||||
purge_soft_delete_on_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
# Default location - West Europe (no US regions)
|
||||
# This can be overridden per resource if needed
|
||||
location = var.azure_region
|
||||
}
|
||||
|
||||
# Variables
|
||||
variable "azure_region" {
|
||||
description = "Azure region (default: westeurope, no US regions allowed)"
|
||||
type = string
|
||||
default = "westeurope"
|
||||
|
||||
validation {
|
||||
condition = !can(regex("^us", var.azure_region))
|
||||
error_message = "US Commercial and Government regions are not allowed. Use European or other non-US regions."
|
||||
}
|
||||
}
|
||||
|
||||
variable "environment" {
|
||||
description = "Environment name (dev, stage, prod)"
|
||||
type = string
|
||||
default = "dev"
|
||||
|
||||
validation {
|
||||
condition = contains(["dev", "stage", "prod"], var.environment)
|
||||
error_message = "Environment must be dev, stage, or prod."
|
||||
}
|
||||
}
|
||||
|
||||
# Outputs
|
||||
output "environment" {
|
||||
description = "Environment name"
|
||||
value = var.environment
|
||||
}
|
||||
|
||||
output "azure_region" {
|
||||
description = "Azure region being used"
|
||||
value = var.azure_region
|
||||
}
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
# Management Group Hierarchy for Cloud for Sovereignty
|
||||
# Root: SOVEREIGN-ORDER-OF-HOSPITALLERS
|
||||
|
||||
variable "management_group_id" {
|
||||
description = "Root management group ID"
|
||||
type = string
|
||||
default = "SOVEREIGN-ORDER-OF-HOSPITALLERS"
|
||||
}
|
||||
|
||||
# Configure Azure Provider
|
||||
provider "azurerm" {
|
||||
features {}
|
||||
}
|
||||
|
||||
# Data source for existing root management group
|
||||
data "azurerm_management_group" "root" {
|
||||
name = var.management_group_id
|
||||
}
|
||||
|
||||
# Landing Zones Management Group
|
||||
resource "azurerm_management_group" "landing_zones" {
|
||||
name = "LandingZones"
|
||||
display_name = "Landing Zones"
|
||||
parent_management_group_id = data.azurerm_management_group.root.id
|
||||
|
||||
subscription_ids = []
|
||||
}
|
||||
|
||||
# Platform Landing Zone
|
||||
resource "azurerm_management_group" "platform" {
|
||||
name = "Platform"
|
||||
display_name = "Platform Landing Zone"
|
||||
parent_management_group_id = azurerm_management_group.landing_zones.id
|
||||
|
||||
subscription_ids = []
|
||||
}
|
||||
|
||||
# Sandbox Landing Zone
|
||||
resource "azurerm_management_group" "sandbox" {
|
||||
name = "Sandbox"
|
||||
display_name = "Sandbox Landing Zone"
|
||||
parent_management_group_id = azurerm_management_group.landing_zones.id
|
||||
|
||||
subscription_ids = []
|
||||
}
|
||||
|
||||
# Workloads Landing Zone
|
||||
resource "azurerm_management_group" "workloads" {
|
||||
name = "Workloads"
|
||||
display_name = "Workload Workloads"
|
||||
parent_management_group_id = azurerm_management_group.landing_zones.id
|
||||
|
||||
subscription_ids = []
|
||||
}
|
||||
|
||||
# Management Management Group
|
||||
resource "azurerm_management_group" "management" {
|
||||
name = "Management"
|
||||
display_name = "Management"
|
||||
parent_management_group_id = data.azurerm_management_group.root.id
|
||||
|
||||
subscription_ids = []
|
||||
}
|
||||
|
||||
# Identity Management Group
|
||||
resource "azurerm_management_group" "identity" {
|
||||
name = "Identity"
|
||||
display_name = "Identity and Access Management"
|
||||
parent_management_group_id = azurerm_management_group.management.id
|
||||
|
||||
subscription_ids = []
|
||||
}
|
||||
|
||||
# Security Management Group
|
||||
resource "azurerm_management_group" "security" {
|
||||
name = "Security"
|
||||
display_name = "Security Operations"
|
||||
parent_management_group_id = azurerm_management_group.management.id
|
||||
|
||||
subscription_ids = []
|
||||
}
|
||||
|
||||
# Monitoring Management Group
|
||||
resource "azurerm_management_group" "monitoring" {
|
||||
name = "Monitoring"
|
||||
display_name = "Centralized Monitoring"
|
||||
parent_management_group_id = azurerm_management_group.management.id
|
||||
|
||||
subscription_ids = []
|
||||
}
|
||||
|
||||
# Connectivity Management Group
|
||||
resource "azurerm_management_group" "connectivity" {
|
||||
name = "Connectivity"
|
||||
display_name = "Connectivity"
|
||||
parent_management_group_id = data.azurerm_management_group.root.id
|
||||
|
||||
subscription_ids = []
|
||||
}
|
||||
|
||||
# Hub Networks Management Group
|
||||
resource "azurerm_management_group" "hub_networks" {
|
||||
name = "HubNetworks"
|
||||
display_name = "Hub Networks"
|
||||
parent_management_group_id = azurerm_management_group.connectivity.id
|
||||
|
||||
subscription_ids = []
|
||||
}
|
||||
|
||||
# Spoke Networks Management Group
|
||||
resource "azurerm_management_group" "spoke_networks" {
|
||||
name = "SpokeNetworks"
|
||||
display_name = "Spoke Networks"
|
||||
parent_management_group_id = azurerm_management_group.connectivity.id
|
||||
|
||||
subscription_ids = []
|
||||
}
|
||||
|
||||
# Outputs
|
||||
output "management_group_hierarchy" {
|
||||
description = "Management group hierarchy"
|
||||
value = {
|
||||
root = data.azurerm_management_group.root.id
|
||||
landing_zones = azurerm_management_group.landing_zones.id
|
||||
platform = azurerm_management_group.platform.id
|
||||
sandbox = azurerm_management_group.sandbox.id
|
||||
workloads = azurerm_management_group.workloads.id
|
||||
management = azurerm_management_group.management.id
|
||||
identity = azurerm_management_group.identity.id
|
||||
security = azurerm_management_group.security.id
|
||||
monitoring = azurerm_management_group.monitoring.id
|
||||
connectivity = azurerm_management_group.connectivity.id
|
||||
hub_networks = azurerm_management_group.hub_networks.id
|
||||
spoke_networks = azurerm_management_group.spoke_networks.id
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
# Variables for Management Groups
|
||||
|
||||
variable "management_group_id" {
|
||||
description = "Root management group ID"
|
||||
type = string
|
||||
default = "SOVEREIGN-ORDER-OF-HOSPITALLERS"
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
# Terraform and Provider Version Constraints for Management Groups
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
|
||||
required_providers {
|
||||
azurerm = {
|
||||
source = "hashicorp/azurerm"
|
||||
version = "~> 3.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
# Regional Landing Zone Module
|
||||
|
||||
Reusable Terraform module for deploying a complete landing zone in a single Azure region, following Cloud for Sovereignty and Well-Architected Framework principles.
|
||||
|
||||
## Features
|
||||
|
||||
- **Hub-and-Spoke Network Architecture**
|
||||
- Hub VNet with gateway, firewall, and management subnets
|
||||
- Spoke VNet with application, database, and storage subnets
|
||||
- VNet peering between hub and spoke
|
||||
|
||||
- **Security**
|
||||
- Azure Firewall for centralized security
|
||||
- Private endpoints for Key Vault and Storage
|
||||
- Network security groups
|
||||
|
||||
- **Compliance**
|
||||
- Customer-managed encryption
|
||||
- Data residency tags
|
||||
- Private endpoints for data sovereignty
|
||||
|
||||
- **Monitoring**
|
||||
- Regional Log Analytics Workspace
|
||||
- Application Insights ready
|
||||
|
||||
## Usage
|
||||
|
||||
```hcl
|
||||
module "west_europe_landing_zone" {
|
||||
source = "../../modules/regional-landing-zone"
|
||||
|
||||
region = "westeurope"
|
||||
environment = "dev"
|
||||
management_group_id = "SOVEREIGN-ORDER-OF-HOSPITALLERS"
|
||||
hub_vnet_address_space = "10.0.0.0/16"
|
||||
spoke_vnet_address_space = "10.1.0.0/16"
|
||||
|
||||
tags = {
|
||||
Project = "the-order"
|
||||
CostCenter = "engineering"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Variables
|
||||
|
||||
- `region` (required): Azure region (must be non-US commercial)
|
||||
- `environment` (required): dev, stage, or prod
|
||||
- `management_group_id` (required): Management group ID
|
||||
- `hub_vnet_address_space` (optional): Hub VNet CIDR (default: 10.0.0.0/16)
|
||||
- `spoke_vnet_address_space` (optional): Spoke VNet CIDR (default: 10.1.0.0/16)
|
||||
- `tags` (optional): Additional tags
|
||||
|
||||
## Outputs
|
||||
|
||||
- `resource_group_name`: Resource group name
|
||||
- `hub_vnet_id`: Hub VNet ID
|
||||
- `spoke_vnet_id`: Spoke VNet ID
|
||||
- `firewall_id`: Azure Firewall ID
|
||||
- `key_vault_id`: Key Vault ID
|
||||
- `log_analytics_workspace_id`: Log Analytics Workspace ID
|
||||
- `storage_account_name`: Storage account name
|
||||
- `subnet_ids`: Map of subnet names to IDs
|
||||
|
||||
@@ -1,342 +0,0 @@
|
||||
# Regional Landing Zone Module
|
||||
# Deploys a complete landing zone for a single Azure region
|
||||
# Follows Cloud for Sovereignty and Well-Architected Framework principles
|
||||
|
||||
variable "region" {
|
||||
description = "Azure region (e.g., westeurope, northeurope)"
|
||||
type = string
|
||||
validation {
|
||||
condition = contains([
|
||||
"westeurope",
|
||||
"northeurope",
|
||||
"uksouth",
|
||||
"switzerlandnorth",
|
||||
"norwayeast",
|
||||
"francecentral",
|
||||
"germanywestcentral"
|
||||
], var.region)
|
||||
error_message = "Region must be a non-US commercial Azure region."
|
||||
}
|
||||
}
|
||||
|
||||
variable "environment" {
|
||||
description = "Environment name (dev, stage, prod)"
|
||||
type = string
|
||||
validation {
|
||||
condition = contains(["dev", "stage", "prod"], var.environment)
|
||||
error_message = "Environment must be dev, stage, or prod."
|
||||
}
|
||||
}
|
||||
|
||||
variable "management_group_id" {
|
||||
description = "Management group ID for this landing zone"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "hub_vnet_address_space" {
|
||||
description = "Address space for hub VNet"
|
||||
type = string
|
||||
default = "10.0.0.0/16"
|
||||
}
|
||||
|
||||
variable "spoke_vnet_address_space" {
|
||||
description = "Address space for spoke VNet"
|
||||
type = string
|
||||
default = "10.1.0.0/16"
|
||||
}
|
||||
|
||||
variable "tags" {
|
||||
description = "Tags to apply to all resources"
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
|
||||
# Local values for naming
|
||||
locals {
|
||||
region_abbrev = {
|
||||
westeurope = "we"
|
||||
northeurope = "ne"
|
||||
uksouth = "uk"
|
||||
switzerlandnorth = "ch"
|
||||
norwayeast = "no"
|
||||
francecentral = "fr"
|
||||
germanywestcentral = "de"
|
||||
}
|
||||
|
||||
env_abbrev = {
|
||||
dev = "dev"
|
||||
stage = "stg"
|
||||
prod = "prd"
|
||||
}
|
||||
|
||||
region_short = lookup(local.region_abbrev, var.region, "we")
|
||||
env_short = lookup(local.env_abbrev, var.environment, "dev")
|
||||
name_prefix = "az-${local.region_short}"
|
||||
|
||||
common_tags = merge(var.tags, {
|
||||
Region = var.region
|
||||
Environment = var.environment
|
||||
DataResidency = var.region
|
||||
ManagedBy = "terraform"
|
||||
LandingZone = "regional"
|
||||
SovereigntyLevel = "high"
|
||||
})
|
||||
}
|
||||
|
||||
# Resource Group
|
||||
resource "azurerm_resource_group" "landing_zone" {
|
||||
name = "${local.name_prefix}-rg-${local.env_short}-lz"
|
||||
location = var.region
|
||||
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
# Hub Virtual Network
|
||||
resource "azurerm_virtual_network" "hub" {
|
||||
name = "${local.name_prefix}-vnet-${local.env_short}-hub"
|
||||
address_space = [var.hub_vnet_address_space]
|
||||
location = var.region
|
||||
resource_group_name = azurerm_resource_group.landing_zone.name
|
||||
|
||||
tags = merge(local.common_tags, {
|
||||
Purpose = "HubNetwork"
|
||||
})
|
||||
}
|
||||
|
||||
# Hub Subnets
|
||||
resource "azurerm_subnet" "hub_gateway" {
|
||||
name = "GatewaySubnet"
|
||||
resource_group_name = azurerm_resource_group.landing_zone.name
|
||||
virtual_network_name = azurerm_virtual_network.hub.name
|
||||
address_prefixes = [cidrsubnet(var.hub_vnet_address_space, 8, 0)]
|
||||
}
|
||||
|
||||
resource "azurerm_subnet" "hub_firewall" {
|
||||
name = "AzureFirewallSubnet"
|
||||
resource_group_name = azurerm_resource_group.landing_zone.name
|
||||
virtual_network_name = azurerm_virtual_network.hub.name
|
||||
address_prefixes = [cidrsubnet(var.hub_vnet_address_space, 8, 1)]
|
||||
}
|
||||
|
||||
resource "azurerm_subnet" "hub_management" {
|
||||
name = "ManagementSubnet"
|
||||
resource_group_name = azurerm_resource_group.landing_zone.name
|
||||
virtual_network_name = azurerm_virtual_network.hub.name
|
||||
address_prefixes = [cidrsubnet(var.hub_vnet_address_space, 8, 2)]
|
||||
}
|
||||
|
||||
# Azure Firewall
|
||||
resource "azurerm_public_ip" "firewall" {
|
||||
name = "${local.name_prefix}-pip-${local.env_short}-fw"
|
||||
location = var.region
|
||||
resource_group_name = azurerm_resource_group.landing_zone.name
|
||||
allocation_method = "Static"
|
||||
sku = "Standard"
|
||||
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
resource "azurerm_firewall" "hub" {
|
||||
name = "${local.name_prefix}-fw-${local.env_short}-hub"
|
||||
location = var.region
|
||||
resource_group_name = azurerm_resource_group.landing_zone.name
|
||||
sku_name = "AZFW_VNet"
|
||||
sku_tier = "Standard"
|
||||
|
||||
ip_configuration {
|
||||
name = "configuration"
|
||||
subnet_id = azurerm_subnet.hub_firewall.id
|
||||
public_ip_address_id = azurerm_public_ip.firewall.id
|
||||
}
|
||||
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
# Spoke Virtual Network
|
||||
resource "azurerm_virtual_network" "spoke" {
|
||||
name = "${local.name_prefix}-vnet-${local.env_short}-spoke"
|
||||
address_space = [var.spoke_vnet_address_space]
|
||||
location = var.region
|
||||
resource_group_name = azurerm_resource_group.landing_zone.name
|
||||
|
||||
tags = merge(local.common_tags, {
|
||||
Purpose = "SpokeNetwork"
|
||||
})
|
||||
}
|
||||
|
||||
# Spoke Subnets
|
||||
resource "azurerm_subnet" "spoke_app" {
|
||||
name = "ApplicationSubnet"
|
||||
resource_group_name = azurerm_resource_group.landing_zone.name
|
||||
virtual_network_name = azurerm_virtual_network.spoke.name
|
||||
address_prefixes = [cidrsubnet(var.spoke_vnet_address_space, 8, 0)]
|
||||
}
|
||||
|
||||
resource "azurerm_subnet" "spoke_db" {
|
||||
name = "DatabaseSubnet"
|
||||
resource_group_name = azurerm_resource_group.landing_zone.name
|
||||
virtual_network_name = azurerm_virtual_network.spoke.name
|
||||
address_prefixes = [cidrsubnet(var.spoke_vnet_address_space, 8, 1)]
|
||||
|
||||
delegation {
|
||||
name = "postgresql-delegation"
|
||||
service_delegation {
|
||||
name = "Microsoft.DBforPostgreSQL/flexibleServers"
|
||||
actions = [
|
||||
"Microsoft.Network/virtualNetworks/subnets/join/action",
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "azurerm_subnet" "spoke_storage" {
|
||||
name = "StorageSubnet"
|
||||
resource_group_name = azurerm_resource_group.landing_zone.name
|
||||
virtual_network_name = azurerm_virtual_network.spoke.name
|
||||
address_prefixes = [cidrsubnet(var.spoke_vnet_address_space, 8, 2)]
|
||||
}
|
||||
|
||||
# VNet Peering: Hub to Spoke
|
||||
resource "azurerm_virtual_network_peering" "hub_to_spoke" {
|
||||
name = "hub-to-spoke"
|
||||
resource_group_name = azurerm_resource_group.landing_zone.name
|
||||
virtual_network_name = azurerm_virtual_network.hub.name
|
||||
remote_virtual_network_id = azurerm_virtual_network.spoke.id
|
||||
allow_forwarded_traffic = true
|
||||
allow_gateway_transit = true
|
||||
}
|
||||
|
||||
resource "azurerm_virtual_network_peering" "spoke_to_hub" {
|
||||
name = "spoke-to-hub"
|
||||
resource_group_name = azurerm_resource_group.landing_zone.name
|
||||
virtual_network_name = azurerm_virtual_network.spoke.name
|
||||
remote_virtual_network_id = azurerm_virtual_network.hub.id
|
||||
allow_forwarded_traffic = true
|
||||
use_remote_gateways = false
|
||||
}
|
||||
|
||||
# Data source for current client config
|
||||
data "azurerm_client_config" "current" {}
|
||||
|
||||
# Key Vault (Regional)
|
||||
resource "azurerm_key_vault" "regional" {
|
||||
name = "${local.name_prefix}-kv-${local.env_short}-${local.region_short}"
|
||||
location = var.region
|
||||
resource_group_name = azurerm_resource_group.landing_zone.name
|
||||
tenant_id = data.azurerm_client_config.current.tenant_id
|
||||
sku_name = "premium"
|
||||
|
||||
# Network ACLs - Private endpoint only
|
||||
network_acls {
|
||||
default_action = "Deny"
|
||||
bypass = "AzureServices"
|
||||
}
|
||||
|
||||
# Enable soft delete and purge protection
|
||||
soft_delete_retention_days = 90
|
||||
purge_protection_enabled = var.environment == "prod"
|
||||
|
||||
tags = merge(local.common_tags, {
|
||||
Purpose = "RegionalSecrets"
|
||||
})
|
||||
}
|
||||
|
||||
# Private Endpoint for Key Vault
|
||||
resource "azurerm_private_endpoint" "key_vault" {
|
||||
name = "${local.name_prefix}-pe-${local.env_short}-kv"
|
||||
location = var.region
|
||||
resource_group_name = azurerm_resource_group.landing_zone.name
|
||||
subnet_id = azurerm_subnet.hub_management.id
|
||||
|
||||
private_service_connection {
|
||||
name = "kv-connection"
|
||||
private_connection_resource_id = azurerm_key_vault.regional.id
|
||||
subresource_names = ["vault"]
|
||||
is_manual_connection = false
|
||||
}
|
||||
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
# Log Analytics Workspace (Regional)
|
||||
resource "azurerm_log_analytics_workspace" "regional" {
|
||||
name = "${local.name_prefix}-law-${local.env_short}-${local.region_short}"
|
||||
location = var.region
|
||||
resource_group_name = azurerm_resource_group.landing_zone.name
|
||||
sku = "PerGB2018"
|
||||
retention_in_days = var.environment == "prod" ? 90 : 30
|
||||
|
||||
tags = merge(local.common_tags, {
|
||||
Purpose = "RegionalLogging"
|
||||
})
|
||||
}
|
||||
|
||||
# Storage Account (Regional)
|
||||
resource "azurerm_storage_account" "regional" {
|
||||
name = "${local.name_prefix}sa${local.env_short}${local.region_short}"
|
||||
resource_group_name = azurerm_resource_group.landing_zone.name
|
||||
location = var.region
|
||||
account_tier = "Standard"
|
||||
account_replication_type = var.environment == "prod" ? "GRS" : "LRS"
|
||||
min_tls_version = "TLS1_2"
|
||||
allow_blob_public_access = false
|
||||
|
||||
# Customer-managed encryption
|
||||
identity {
|
||||
type = "SystemAssigned"
|
||||
}
|
||||
|
||||
blob_properties {
|
||||
versioning_enabled = true
|
||||
delete_retention_policy {
|
||||
days = var.environment == "prod" ? 90 : 30
|
||||
}
|
||||
}
|
||||
|
||||
tags = merge(local.common_tags, {
|
||||
Purpose = "RegionalStorage"
|
||||
})
|
||||
}
|
||||
|
||||
# Private Endpoint for Storage Account
|
||||
resource "azurerm_private_endpoint" "storage" {
|
||||
name = "${local.name_prefix}-pe-${local.env_short}-st"
|
||||
location = var.region
|
||||
resource_group_name = azurerm_resource_group.landing_zone.name
|
||||
subnet_id = azurerm_subnet.spoke_storage.id
|
||||
|
||||
private_service_connection {
|
||||
name = "storage-connection"
|
||||
private_connection_resource_id = azurerm_storage_account.regional.id
|
||||
subresource_names = ["blob"]
|
||||
is_manual_connection = false
|
||||
}
|
||||
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
# Outputs
|
||||
output "resource_group_name" {
|
||||
value = azurerm_resource_group.landing_zone.name
|
||||
}
|
||||
|
||||
output "hub_vnet_id" {
|
||||
value = azurerm_virtual_network.hub.id
|
||||
}
|
||||
|
||||
output "spoke_vnet_id" {
|
||||
value = azurerm_virtual_network.spoke.id
|
||||
}
|
||||
|
||||
output "key_vault_id" {
|
||||
value = azurerm_key_vault.regional.id
|
||||
}
|
||||
|
||||
output "log_analytics_workspace_id" {
|
||||
value = azurerm_log_analytics_workspace.regional.workspace_id
|
||||
}
|
||||
|
||||
output "storage_account_name" {
|
||||
value = azurerm_storage_account.regional.name
|
||||
}
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
# Outputs for Regional Landing Zone Module
|
||||
|
||||
output "resource_group_name" {
|
||||
description = "Name of the resource group"
|
||||
value = azurerm_resource_group.landing_zone.name
|
||||
}
|
||||
|
||||
output "resource_group_id" {
|
||||
description = "ID of the resource group"
|
||||
value = azurerm_resource_group.landing_zone.id
|
||||
}
|
||||
|
||||
output "hub_vnet_id" {
|
||||
description = "ID of the hub virtual network"
|
||||
value = azurerm_virtual_network.hub.id
|
||||
}
|
||||
|
||||
output "hub_vnet_name" {
|
||||
description = "Name of the hub virtual network"
|
||||
value = azurerm_virtual_network.hub.name
|
||||
}
|
||||
|
||||
output "spoke_vnet_id" {
|
||||
description = "ID of the spoke virtual network"
|
||||
value = azurerm_virtual_network.spoke.id
|
||||
}
|
||||
|
||||
output "spoke_vnet_name" {
|
||||
description = "Name of the spoke virtual network"
|
||||
value = azurerm_virtual_network.spoke.name
|
||||
}
|
||||
|
||||
output "firewall_id" {
|
||||
description = "ID of the Azure Firewall"
|
||||
value = azurerm_firewall.hub.id
|
||||
}
|
||||
|
||||
output "firewall_private_ip" {
|
||||
description = "Private IP address of the Azure Firewall"
|
||||
value = azurerm_firewall.hub.ip_configuration[0].private_ip_address
|
||||
}
|
||||
|
||||
output "key_vault_id" {
|
||||
description = "ID of the Key Vault"
|
||||
value = azurerm_key_vault.regional.id
|
||||
}
|
||||
|
||||
output "key_vault_name" {
|
||||
description = "Name of the Key Vault"
|
||||
value = azurerm_key_vault.regional.name
|
||||
}
|
||||
|
||||
output "key_vault_uri" {
|
||||
description = "URI of the Key Vault"
|
||||
value = azurerm_key_vault.regional.vault_uri
|
||||
}
|
||||
|
||||
output "log_analytics_workspace_id" {
|
||||
description = "ID of the Log Analytics Workspace"
|
||||
value = azurerm_log_analytics_workspace.regional.workspace_id
|
||||
}
|
||||
|
||||
output "log_analytics_workspace_name" {
|
||||
description = "Name of the Log Analytics Workspace"
|
||||
value = azurerm_log_analytics_workspace.regional.name
|
||||
}
|
||||
|
||||
output "storage_account_name" {
|
||||
description = "Name of the storage account"
|
||||
value = azurerm_storage_account.regional.name
|
||||
}
|
||||
|
||||
output "storage_account_id" {
|
||||
description = "ID of the storage account"
|
||||
value = azurerm_storage_account.regional.id
|
||||
}
|
||||
|
||||
output "storage_account_primary_endpoint" {
|
||||
description = "Primary endpoint of the storage account"
|
||||
value = azurerm_storage_account.regional.primary_blob_endpoint
|
||||
}
|
||||
|
||||
output "subnet_ids" {
|
||||
description = "Map of subnet names to IDs"
|
||||
value = {
|
||||
hub_gateway = azurerm_subnet.hub_gateway.id
|
||||
hub_firewall = azurerm_subnet.hub_firewall.id
|
||||
hub_management = azurerm_subnet.hub_management.id
|
||||
spoke_app = azurerm_subnet.spoke_app.id
|
||||
spoke_db = azurerm_subnet.spoke_db.id
|
||||
spoke_storage = azurerm_subnet.spoke_storage.id
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
# Variables for Regional Landing Zone Module
|
||||
|
||||
variable "region" {
|
||||
description = "Azure region (e.g., westeurope, northeurope)"
|
||||
type = string
|
||||
validation {
|
||||
condition = contains([
|
||||
"westeurope",
|
||||
"northeurope",
|
||||
"uksouth",
|
||||
"switzerlandnorth",
|
||||
"norwayeast",
|
||||
"francecentral",
|
||||
"germanywestcentral"
|
||||
], var.region)
|
||||
error_message = "Region must be a non-US commercial Azure region."
|
||||
}
|
||||
}
|
||||
|
||||
variable "environment" {
|
||||
description = "Environment name (dev, stage, prod)"
|
||||
type = string
|
||||
validation {
|
||||
condition = contains(["dev", "stage", "prod"], var.environment)
|
||||
error_message = "Environment must be dev, stage, or prod."
|
||||
}
|
||||
}
|
||||
|
||||
variable "management_group_id" {
|
||||
description = "Management group ID for this landing zone"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "hub_vnet_address_space" {
|
||||
description = "Address space for hub VNet"
|
||||
type = string
|
||||
default = "10.0.0.0/16"
|
||||
}
|
||||
|
||||
variable "spoke_vnet_address_space" {
|
||||
description = "Address space for spoke VNet"
|
||||
type = string
|
||||
default = "10.1.0.0/16"
|
||||
}
|
||||
|
||||
variable "tags" {
|
||||
description = "Tags to apply to all resources"
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
# Terraform and Provider Version Constraints for Regional Landing Zone Module
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
|
||||
required_providers {
|
||||
azurerm = {
|
||||
source = "hashicorp/azurerm"
|
||||
version = "~> 3.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Configure Azure Provider (inherited from parent)
|
||||
# Provider configuration should be set in the calling module
|
||||
|
||||
@@ -1,395 +0,0 @@
|
||||
/**
|
||||
* Well-Architected Framework Module
|
||||
* Implements all five pillars: Cost, Operations, Performance, Reliability, Security
|
||||
* Cloud for Sovereignty compliant
|
||||
*/
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
required_providers {
|
||||
azurerm = {
|
||||
source = "hashicorp/azurerm"
|
||||
version = "~> 3.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Data sources
|
||||
data "azurerm_client_config" "current" {}
|
||||
data "azurerm_subscription" "current" {}
|
||||
|
||||
# Local values
|
||||
locals {
|
||||
name_prefix = var.name_prefix != "" ? var.name_prefix : "the-order"
|
||||
env_short = var.environment == "production" ? "prod" : var.environment == "staging" ? "stg" : "dev"
|
||||
|
||||
# Standard tags for cost optimization
|
||||
common_tags = merge(var.tags, {
|
||||
Environment = var.environment
|
||||
Project = "the-order"
|
||||
CostCenter = var.cost_center
|
||||
Owner = var.owner
|
||||
DataClassification = var.data_classification
|
||||
Sovereignty = "required"
|
||||
ManagedBy = "terraform"
|
||||
WellArchitected = "true"
|
||||
})
|
||||
|
||||
# Regions for sovereignty
|
||||
allowed_regions = var.allowed_regions != [] ? var.allowed_regions : [
|
||||
"westeurope",
|
||||
"northeurope",
|
||||
"uksouth",
|
||||
"switzerlandnorth",
|
||||
"norwayeast",
|
||||
"francecentral",
|
||||
"germanywestcentral"
|
||||
]
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# COST OPTIMIZATION
|
||||
# ============================================================================
|
||||
|
||||
# Budget and cost management
|
||||
resource "azurerm_consumption_budget_subscription" "main" {
|
||||
count = var.enable_cost_management ? 1 : 0
|
||||
name = "${local.name_prefix}-budget-${local.env_short}"
|
||||
subscription_id = data.azurerm_subscription.current.id
|
||||
|
||||
amount = var.monthly_budget_amount
|
||||
time_grain = "Monthly"
|
||||
|
||||
time_period {
|
||||
start_date = formatdate("YYYY-MM-01T00:00:00Z", timestamp())
|
||||
end_date = timeadd(formatdate("YYYY-MM-01T00:00:00Z", timestamp()), "1y")
|
||||
}
|
||||
|
||||
notification {
|
||||
enabled = true
|
||||
threshold = 50
|
||||
operator = "GreaterThan"
|
||||
threshold_type = "Actual"
|
||||
contact_emails = var.budget_alert_emails
|
||||
}
|
||||
|
||||
notification {
|
||||
enabled = true
|
||||
threshold = 75
|
||||
operator = "GreaterThan"
|
||||
threshold_type = "Actual"
|
||||
contact_emails = var.budget_alert_emails
|
||||
}
|
||||
|
||||
notification {
|
||||
enabled = true
|
||||
threshold = 90
|
||||
operator = "GreaterThan"
|
||||
threshold_type = "Actual"
|
||||
contact_emails = var.budget_alert_emails
|
||||
}
|
||||
|
||||
notification {
|
||||
enabled = true
|
||||
threshold = 100
|
||||
operator = "GreaterThan"
|
||||
threshold_type = "Actual"
|
||||
contact_emails = var.budget_alert_emails
|
||||
}
|
||||
}
|
||||
|
||||
# Cost Management export
|
||||
resource "azurerm_cost_management_export_resource_group" "main" {
|
||||
count = var.enable_cost_management ? 1 : 0
|
||||
name = "${local.name_prefix}-cost-export-${local.env_short}"
|
||||
resource_group_id = var.resource_group_id
|
||||
recurrence_type = "Monthly"
|
||||
recurrence_period_start_date = formatdate("YYYY-MM-01T00:00:00Z", timestamp())
|
||||
recurrence_period_end_date = timeadd(formatdate("YYYY-MM-01T00:00:00Z", timestamp()), "1y")
|
||||
|
||||
export_data_storage_location {
|
||||
container_id = var.cost_export_storage_container_id
|
||||
root_folder_path = "cost-exports"
|
||||
}
|
||||
|
||||
export_data_options {
|
||||
type = "Usage"
|
||||
time_frame = "MonthToDate"
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# OPERATIONAL EXCELLENCE
|
||||
# ============================================================================
|
||||
|
||||
# Log Analytics Workspace for centralized logging
|
||||
resource "azurerm_log_analytics_workspace" "main" {
|
||||
name = "${local.name_prefix}-logs-${local.env_short}-${substr(var.region, 0, 6)}"
|
||||
location = var.region
|
||||
resource_group_name = var.resource_group_name
|
||||
sku = "PerGB2018"
|
||||
retention_in_days = var.environment == "production" ? 90 : 30
|
||||
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
# Application Insights for APM
|
||||
resource "azurerm_application_insights" "main" {
|
||||
name = "${local.name_prefix}-appinsights-${local.env_short}-${substr(var.region, 0, 6)}"
|
||||
location = var.region
|
||||
resource_group_name = var.resource_group_name
|
||||
application_type = "web"
|
||||
workspace_id = azurerm_log_analytics_workspace.main.id
|
||||
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
# Automation Account for runbooks
|
||||
resource "azurerm_automation_account" "main" {
|
||||
count = var.enable_automation ? 1 : 0
|
||||
name = "${local.name_prefix}-automation-${local.env_short}-${substr(var.region, 0, 6)}"
|
||||
location = var.region
|
||||
resource_group_name = var.resource_group_name
|
||||
sku_name = "Basic"
|
||||
|
||||
identity {
|
||||
type = "SystemAssigned"
|
||||
}
|
||||
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# PERFORMANCE EFFICIENCY
|
||||
# ============================================================================
|
||||
|
||||
# Azure Front Door for global load balancing and CDN
|
||||
resource "azurerm_front_door" "main" {
|
||||
count = var.enable_front_door ? 1 : 0
|
||||
name = "${local.name_prefix}-fd-${local.env_short}"
|
||||
resource_group_name = var.resource_group_name
|
||||
location = "Global"
|
||||
|
||||
routing_rule {
|
||||
name = "default-rule"
|
||||
accepted_protocols = ["Https"]
|
||||
patterns_to_match = ["/*"]
|
||||
frontend_endpoints = ["${local.name_prefix}-fd-${local.env_short}"]
|
||||
forwarding_configuration {
|
||||
forwarding_protocol = "HttpsOnly"
|
||||
backend_pool_name = "default-backend"
|
||||
}
|
||||
}
|
||||
|
||||
backend_pool_load_balancing {
|
||||
name = "default-load-balancer"
|
||||
}
|
||||
|
||||
backend_pool_health_probe {
|
||||
name = "default-health-probe"
|
||||
}
|
||||
|
||||
backend_pool {
|
||||
name = "default-backend"
|
||||
backend {
|
||||
host_header = var.backend_host_header
|
||||
address = var.backend_address
|
||||
http_port = 80
|
||||
https_port = 443
|
||||
}
|
||||
load_balancing_name = "default-load-balancer"
|
||||
health_probe_name = "default-health-probe"
|
||||
}
|
||||
|
||||
frontend_endpoint {
|
||||
name = "${local.name_prefix}-fd-${local.env_short}"
|
||||
host_name = "${local.name_prefix}-fd-${local.env_short}.azurefd.net"
|
||||
}
|
||||
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
# Redis Cache for application caching
|
||||
resource "azurerm_redis_cache" "main" {
|
||||
count = var.enable_redis_cache ? 1 : 0
|
||||
name = "${local.name_prefix}-redis-${local.env_short}-${substr(var.region, 0, 6)}"
|
||||
location = var.region
|
||||
resource_group_name = var.resource_group_name
|
||||
capacity = var.redis_capacity
|
||||
family = var.redis_family
|
||||
sku_name = "${var.redis_family}${var.redis_capacity}"
|
||||
enable_non_ssl_port = false
|
||||
minimum_tls_version = "1.2"
|
||||
|
||||
redis_configuration {
|
||||
maxmemory_reserved = 2
|
||||
maxmemory_delta = 2
|
||||
maxmemory_policy = "allkeys-lru"
|
||||
}
|
||||
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# RELIABILITY
|
||||
# ============================================================================
|
||||
|
||||
# Recovery Services Vault for backups
|
||||
resource "azurerm_recovery_services_vault" "main" {
|
||||
count = var.enable_backup ? 1 : 0
|
||||
name = "${local.name_prefix}-rsv-${local.env_short}-${substr(var.region, 0, 6)}"
|
||||
location = var.region
|
||||
resource_group_name = var.resource_group_name
|
||||
sku = "Standard"
|
||||
soft_delete_enabled = true
|
||||
|
||||
identity {
|
||||
type = "SystemAssigned"
|
||||
}
|
||||
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
# Backup policy
|
||||
resource "azurerm_backup_policy_vm" "main" {
|
||||
count = var.enable_backup ? 1 : 0
|
||||
name = "${local.name_prefix}-backup-policy-${local.env_short}"
|
||||
resource_group_name = var.resource_group_name
|
||||
recovery_vault_name = azurerm_recovery_services_vault.main[0].name
|
||||
|
||||
timezone = "UTC"
|
||||
|
||||
backup {
|
||||
frequency = "Daily"
|
||||
time = "23:00"
|
||||
}
|
||||
|
||||
retention_daily {
|
||||
count = var.environment == "production" ? 30 : 7
|
||||
}
|
||||
|
||||
retention_weekly {
|
||||
count = var.environment == "production" ? 12 : 4
|
||||
weekdays = ["Sunday"]
|
||||
}
|
||||
|
||||
retention_monthly {
|
||||
count = var.environment == "production" ? 12 : 3
|
||||
months = ["January", "July"]
|
||||
weekdays = ["Sunday"]
|
||||
weeks = ["First"]
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# SECURITY
|
||||
# ============================================================================
|
||||
|
||||
# Key Vault for secrets management (if not already created)
|
||||
resource "azurerm_key_vault" "main" {
|
||||
count = var.create_key_vault ? 1 : 0
|
||||
name = "${local.name_prefix}-kv-${local.env_short}-${substr(var.region, 0, 6)}"
|
||||
location = var.region
|
||||
resource_group_name = var.resource_group_name
|
||||
tenant_id = data.azurerm_client_config.current.tenant_id
|
||||
sku_name = "premium"
|
||||
|
||||
# Network ACLs - Private endpoint only
|
||||
network_acls {
|
||||
default_action = "Deny"
|
||||
bypass = "AzureServices"
|
||||
}
|
||||
|
||||
# Enable soft delete and purge protection
|
||||
soft_delete_retention_days = 90
|
||||
purge_protection_enabled = var.environment == "production"
|
||||
|
||||
tags = merge(local.common_tags, {
|
||||
Purpose = "SecretsManagement"
|
||||
})
|
||||
}
|
||||
|
||||
# Microsoft Defender for Cloud
|
||||
resource "azurerm_security_center_subscription_pricing" "main" {
|
||||
count = var.enable_defender ? 1 : 0
|
||||
tier = "Standard"
|
||||
subplan = "P2"
|
||||
resource_type = "VirtualMachines"
|
||||
}
|
||||
|
||||
# DDoS Protection Plan
|
||||
resource "azurerm_network_ddos_protection_plan" "main" {
|
||||
count = var.enable_ddos_protection ? 1 : 0
|
||||
name = "${local.name_prefix}-ddos-${local.env_short}-${substr(var.region, 0, 6)}"
|
||||
location = var.region
|
||||
resource_group_name = var.resource_group_name
|
||||
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# CLOUD FOR SOVEREIGNTY
|
||||
# ============================================================================
|
||||
|
||||
# Azure Policy for data residency enforcement
|
||||
resource "azurerm_policy_definition" "data_residency" {
|
||||
count = var.enable_sovereignty_policies ? 1 : 0
|
||||
name = "${local.name_prefix}-data-residency-${local.env_short}"
|
||||
policy_type = "Custom"
|
||||
mode = "All"
|
||||
display_name = "Enforce Data Residency - ${var.environment}"
|
||||
|
||||
policy_rule = jsonencode({
|
||||
if = {
|
||||
allOf = [
|
||||
{
|
||||
field = "location"
|
||||
notIn = local.allowed_regions
|
||||
}
|
||||
]
|
||||
}
|
||||
then = {
|
||||
effect = "deny"
|
||||
}
|
||||
})
|
||||
|
||||
metadata = jsonencode({
|
||||
category = "Sovereignty"
|
||||
})
|
||||
}
|
||||
|
||||
# Policy assignment
|
||||
resource "azurerm_policy_assignment" "data_residency" {
|
||||
count = var.enable_sovereignty_policies ? 1 : 0
|
||||
name = "${local.name_prefix}-data-residency-assignment-${local.env_short}"
|
||||
scope = var.management_group_id != "" ? var.management_group_id : data.azurerm_subscription.current.id
|
||||
policy_definition_id = azurerm_policy_definition.data_residency[0].id
|
||||
display_name = "Enforce Data Residency - ${var.environment}"
|
||||
|
||||
identity {
|
||||
type = "SystemAssigned"
|
||||
}
|
||||
}
|
||||
|
||||
# Outputs
|
||||
output "log_analytics_workspace_id" {
|
||||
value = azurerm_log_analytics_workspace.main.id
|
||||
description = "Log Analytics Workspace ID"
|
||||
}
|
||||
|
||||
output "application_insights_instrumentation_key" {
|
||||
value = azurerm_application_insights.main.instrumentation_key
|
||||
sensitive = true
|
||||
description = "Application Insights Instrumentation Key"
|
||||
}
|
||||
|
||||
output "redis_cache_hostname" {
|
||||
value = var.enable_redis_cache ? azurerm_redis_cache.main[0].hostname : null
|
||||
description = "Redis Cache Hostname"
|
||||
}
|
||||
|
||||
output "key_vault_uri" {
|
||||
value = var.create_key_vault ? azurerm_key_vault.main[0].vault_uri : null
|
||||
description = "Key Vault URI"
|
||||
}
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
variable "name_prefix" {
|
||||
description = "Prefix for resource names"
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "environment" {
|
||||
description = "Environment name (dev, staging, production)"
|
||||
type = string
|
||||
validation {
|
||||
condition = contains(["dev", "staging", "production"], var.environment)
|
||||
error_message = "Environment must be dev, staging, or production."
|
||||
}
|
||||
}
|
||||
|
||||
variable "region" {
|
||||
description = "Azure region"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "resource_group_name" {
|
||||
description = "Resource group name"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "resource_group_id" {
|
||||
description = "Resource group ID"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "tags" {
|
||||
description = "Additional tags"
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "cost_center" {
|
||||
description = "Cost center for cost allocation"
|
||||
type = string
|
||||
default = "legal-services"
|
||||
}
|
||||
|
||||
variable "owner" {
|
||||
description = "Resource owner"
|
||||
type = string
|
||||
default = "legal-team"
|
||||
}
|
||||
|
||||
variable "data_classification" {
|
||||
description = "Data classification level"
|
||||
type = string
|
||||
default = "confidential"
|
||||
}
|
||||
|
||||
# Cost Optimization
|
||||
variable "enable_cost_management" {
|
||||
description = "Enable cost management features"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "monthly_budget_amount" {
|
||||
description = "Monthly budget amount"
|
||||
type = number
|
||||
default = 10000
|
||||
}
|
||||
|
||||
variable "budget_alert_emails" {
|
||||
description = "Email addresses for budget alerts"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "cost_export_storage_container_id" {
|
||||
description = "Storage container ID for cost exports"
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
# Operational Excellence
|
||||
variable "enable_automation" {
|
||||
description = "Enable automation account"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
# Performance Efficiency
|
||||
variable "enable_front_door" {
|
||||
description = "Enable Azure Front Door"
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "backend_host_header" {
|
||||
description = "Backend host header for Front Door"
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "backend_address" {
|
||||
description = "Backend address for Front Door"
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "enable_redis_cache" {
|
||||
description = "Enable Redis cache"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "redis_capacity" {
|
||||
description = "Redis cache capacity"
|
||||
type = number
|
||||
default = 1
|
||||
}
|
||||
|
||||
variable "redis_family" {
|
||||
description = "Redis cache family (C or P)"
|
||||
type = string
|
||||
default = "C"
|
||||
validation {
|
||||
condition = contains(["C", "P"], var.redis_family)
|
||||
error_message = "Redis family must be C or P."
|
||||
}
|
||||
}
|
||||
|
||||
# Reliability
|
||||
variable "enable_backup" {
|
||||
description = "Enable backup services"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
# Security
|
||||
variable "create_key_vault" {
|
||||
description = "Create Key Vault (if not already exists)"
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "enable_defender" {
|
||||
description = "Enable Microsoft Defender for Cloud"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "enable_ddos_protection" {
|
||||
description = "Enable DDoS Protection"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
# Cloud for Sovereignty
|
||||
variable "enable_sovereignty_policies" {
|
||||
description = "Enable sovereignty policies"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "allowed_regions" {
|
||||
description = "List of allowed regions for data residency"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "management_group_id" {
|
||||
description = "Management group ID for policy assignment"
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
# Multi-Region Landing Zone Deployment
|
||||
|
||||
Deploys Cloud for Sovereignty landing zones across all non-US commercial Azure regions.
|
||||
|
||||
## Supported Regions
|
||||
|
||||
1. **West Europe** (Netherlands) - Primary region
|
||||
2. **North Europe** (Ireland) - Secondary region
|
||||
3. **UK South** (London) - UK-specific workloads
|
||||
4. **Switzerland North** (Zurich) - Swiss-specific workloads
|
||||
5. **Norway East** (Oslo) - Nordic-specific workloads
|
||||
6. **France Central** (Paris) - French-specific workloads
|
||||
7. **Germany West Central** (Frankfurt) - German-specific workloads
|
||||
|
||||
## Architecture
|
||||
|
||||
Each region includes:
|
||||
- Hub Virtual Network (gateway, firewall, management)
|
||||
- Spoke Virtual Network (application, database, storage)
|
||||
- Azure Firewall (centralized security)
|
||||
- Key Vault (regional secrets with private endpoints)
|
||||
- Log Analytics Workspace (regional logging)
|
||||
- Storage Account (regional storage with private endpoints)
|
||||
|
||||
## Usage
|
||||
|
||||
### Deploy All Regions
|
||||
|
||||
```bash
|
||||
cd infra/terraform/multi-region
|
||||
terraform init
|
||||
terraform plan -var="environment=dev" -var="management_group_id=SOVEREIGN-ORDER-OF-HOSPITALLERS"
|
||||
terraform apply
|
||||
```
|
||||
|
||||
### Deploy Specific Regions
|
||||
|
||||
```bash
|
||||
terraform plan \
|
||||
-var="environment=dev" \
|
||||
-var="deploy_all_regions=false" \
|
||||
-var='regions_to_deploy=["westeurope", "northeurope"]'
|
||||
terraform apply
|
||||
```
|
||||
|
||||
## Variables
|
||||
|
||||
- `environment`: dev, stage, or prod
|
||||
- `management_group_id`: Root management group ID
|
||||
- `deploy_all_regions`: Deploy to all supported regions (default: true)
|
||||
- `regions_to_deploy`: Specific regions if deploy_all_regions is false
|
||||
|
||||
## Outputs
|
||||
|
||||
- `deployed_regions`: List of deployed regions
|
||||
- `regional_resource_groups`: Resource group names per region
|
||||
- `regional_key_vaults`: Key Vault names per region
|
||||
- `regional_storage_accounts`: Storage account names per region
|
||||
- `deployment_summary`: Complete deployment summary
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
# Multi-Region Landing Zone Deployment
|
||||
# Deploys landing zones across all non-US commercial Azure regions
|
||||
# Uses the regional-landing-zone module
|
||||
|
||||
variable "environment" {
|
||||
description = "Environment name (dev, stage, prod)"
|
||||
type = string
|
||||
default = "dev"
|
||||
}
|
||||
|
||||
variable "management_group_id" {
|
||||
description = "Root management group ID"
|
||||
type = string
|
||||
default = "SOVEREIGN-ORDER-OF-HOSPITALLERS"
|
||||
}
|
||||
|
||||
variable "deploy_all_regions" {
|
||||
description = "Deploy to all supported regions"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "regions_to_deploy" {
|
||||
description = "Specific regions to deploy (if deploy_all_regions is false)"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
# Supported non-US commercial regions
|
||||
locals {
|
||||
supported_regions = [
|
||||
"westeurope", # Netherlands - Primary
|
||||
"northeurope", # Ireland - Secondary
|
||||
"uksouth", # London - UK workloads
|
||||
"switzerlandnorth", # Zurich - Swiss workloads
|
||||
"norwayeast", # Oslo - Nordic workloads
|
||||
"francecentral", # Paris - French workloads
|
||||
"germanywestcentral" # Frankfurt - German workloads
|
||||
]
|
||||
|
||||
regions = var.deploy_all_regions ? local.supported_regions : var.regions_to_deploy
|
||||
|
||||
# Hub VNet address spaces per region
|
||||
hub_address_spaces = {
|
||||
westeurope = "10.0.0.0/16"
|
||||
northeurope = "10.10.0.0/16"
|
||||
uksouth = "10.20.0.0/16"
|
||||
switzerlandnorth = "10.30.0.0/16"
|
||||
norwayeast = "10.40.0.0/16"
|
||||
francecentral = "10.50.0.0/16"
|
||||
germanywestcentral = "10.60.0.0/16"
|
||||
}
|
||||
|
||||
# Spoke VNet address spaces per region
|
||||
spoke_address_spaces = {
|
||||
westeurope = "10.1.0.0/16"
|
||||
northeurope = "10.11.0.0/16"
|
||||
uksouth = "10.21.0.0/16"
|
||||
switzerlandnorth = "10.31.0.0/16"
|
||||
norwayeast = "10.41.0.0/16"
|
||||
francecentral = "10.51.0.0/16"
|
||||
germanywestcentral = "10.61.0.0/16"
|
||||
}
|
||||
|
||||
common_tags = {
|
||||
Environment = var.environment
|
||||
Project = "the-order"
|
||||
ManagedBy = "terraform"
|
||||
SovereigntyLevel = "high"
|
||||
DataClassification = "confidential"
|
||||
Compliance = "gdpr,eidas"
|
||||
}
|
||||
}
|
||||
|
||||
# Deploy regional landing zones
|
||||
module "regional_landing_zones" {
|
||||
source = "../modules/regional-landing-zone"
|
||||
|
||||
for_each = toset(local.regions)
|
||||
|
||||
region = each.value
|
||||
environment = var.environment
|
||||
management_group_id = var.management_group_id
|
||||
hub_vnet_address_space = local.hub_address_spaces[each.value]
|
||||
spoke_vnet_address_space = local.spoke_address_spaces[each.value]
|
||||
tags = merge(local.common_tags, {
|
||||
Region = each.value
|
||||
})
|
||||
}
|
||||
|
||||
# Outputs
|
||||
output "deployed_regions" {
|
||||
description = "List of deployed regions"
|
||||
value = local.regions
|
||||
}
|
||||
|
||||
output "regional_resource_groups" {
|
||||
description = "Resource group names per region"
|
||||
value = {
|
||||
for region, module in module.regional_landing_zones :
|
||||
region => module.resource_group_name
|
||||
}
|
||||
}
|
||||
|
||||
output "regional_key_vaults" {
|
||||
description = "Key Vault IDs per region"
|
||||
value = {
|
||||
for region, module in module.regional_landing_zones :
|
||||
region => module.key_vault_id
|
||||
}
|
||||
}
|
||||
|
||||
output "regional_storage_accounts" {
|
||||
description = "Storage account names per region"
|
||||
value = {
|
||||
for region, module in module.regional_landing_zones :
|
||||
region => module.storage_account_name
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
# Outputs for Multi-Region Landing Zone Deployment
|
||||
|
||||
output "deployed_regions" {
|
||||
description = "List of deployed regions"
|
||||
value = local.regions
|
||||
}
|
||||
|
||||
output "regional_resource_groups" {
|
||||
description = "Resource group names per region"
|
||||
value = {
|
||||
for region, module in module.regional_landing_zones :
|
||||
region => module.resource_group_name
|
||||
}
|
||||
}
|
||||
|
||||
output "regional_key_vaults" {
|
||||
description = "Key Vault names per region"
|
||||
value = {
|
||||
for region, module in module.regional_landing_zones :
|
||||
region => module.key_vault_name
|
||||
}
|
||||
}
|
||||
|
||||
output "regional_storage_accounts" {
|
||||
description = "Storage account names per region"
|
||||
value = {
|
||||
for region, module in module.regional_landing_zones :
|
||||
region => module.storage_account_name
|
||||
}
|
||||
}
|
||||
|
||||
output "regional_log_analytics_workspaces" {
|
||||
description = "Log Analytics Workspace names per region"
|
||||
value = {
|
||||
for region, module in module.regional_landing_zones :
|
||||
region => module.log_analytics_workspace_name
|
||||
}
|
||||
}
|
||||
|
||||
output "regional_hub_vnets" {
|
||||
description = "Hub VNet names per region"
|
||||
value = {
|
||||
for region, module in module.regional_landing_zones :
|
||||
region => module.hub_vnet_name
|
||||
}
|
||||
}
|
||||
|
||||
output "regional_spoke_vnets" {
|
||||
description = "Spoke VNet names per region"
|
||||
value = {
|
||||
for region, module in module.regional_landing_zones :
|
||||
region => module.spoke_vnet_name
|
||||
}
|
||||
}
|
||||
|
||||
output "deployment_summary" {
|
||||
description = "Summary of deployment"
|
||||
value = {
|
||||
total_regions = length(local.regions)
|
||||
regions = local.regions
|
||||
environment = var.environment
|
||||
management_group = var.management_group_id
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
# Variables for Multi-Region Landing Zone Deployment
|
||||
|
||||
variable "environment" {
|
||||
description = "Environment name (dev, stage, prod)"
|
||||
type = string
|
||||
default = "dev"
|
||||
validation {
|
||||
condition = contains(["dev", "stage", "prod"], var.environment)
|
||||
error_message = "Environment must be dev, stage, or prod."
|
||||
}
|
||||
}
|
||||
|
||||
variable "management_group_id" {
|
||||
description = "Root management group ID"
|
||||
type = string
|
||||
default = "SOVEREIGN-ORDER-OF-HOSPITALLERS"
|
||||
}
|
||||
|
||||
variable "deploy_all_regions" {
|
||||
description = "Deploy to all supported regions"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "regions_to_deploy" {
|
||||
description = "Specific regions to deploy (if deploy_all_regions is false)"
|
||||
type = list(string)
|
||||
default = []
|
||||
validation {
|
||||
condition = alltrue([
|
||||
for region in var.regions_to_deploy : contains([
|
||||
"westeurope",
|
||||
"northeurope",
|
||||
"uksouth",
|
||||
"switzerlandnorth",
|
||||
"norwayeast",
|
||||
"francecentral",
|
||||
"germanywestcentral"
|
||||
], region)
|
||||
])
|
||||
error_message = "All regions must be non-US commercial Azure regions."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
# Terraform and Provider Version Constraints for Multi-Region Deployment
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
|
||||
required_providers {
|
||||
azurerm = {
|
||||
source = "hashicorp/azurerm"
|
||||
version = "~> 3.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
# Azure-specific outputs for integration with Kubernetes and services
|
||||
|
||||
output "subscription_id" {
|
||||
description = "Azure subscription ID"
|
||||
value = data.azurerm_subscription.current.subscription_id
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "tenant_id" {
|
||||
description = "Azure tenant ID"
|
||||
value = data.azurerm_client_config.current.tenant_id
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "resource_group_name" {
|
||||
description = "Main resource group name"
|
||||
value = azurerm_resource_group.main.name
|
||||
}
|
||||
|
||||
output "storage_account_name" {
|
||||
description = "Application data storage account name"
|
||||
value = azurerm_storage_account.app_data.name
|
||||
}
|
||||
|
||||
output "storage_account_primary_key" {
|
||||
description = "Storage account primary access key"
|
||||
value = azurerm_storage_account.app_data.primary_access_key
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "key_vault_name" {
|
||||
description = "Key Vault name"
|
||||
value = azurerm_key_vault.main.name
|
||||
}
|
||||
|
||||
output "key_vault_uri" {
|
||||
description = "Key Vault URI"
|
||||
value = azurerm_key_vault.main.vault_uri
|
||||
}
|
||||
|
||||
# CDN Outputs
|
||||
output "cdn_storage_account_name" {
|
||||
description = "CDN storage account name"
|
||||
value = azurerm_storage_account.cdn_images.name
|
||||
}
|
||||
|
||||
output "cdn_blob_url" {
|
||||
description = "CDN blob storage URL"
|
||||
value = "https://${azurerm_storage_account.cdn_images.name}.blob.core.windows.net/${azurerm_storage_container.cdn_images.name}/"
|
||||
}
|
||||
|
||||
output "cdn_endpoint_url" {
|
||||
description = "CDN endpoint URL"
|
||||
value = "https://${azurerm_cdn_endpoint.cdn_images.host_name}/${azurerm_storage_container.cdn_images.name}/"
|
||||
}
|
||||
|
||||
# Database Outputs
|
||||
output "database_fqdn" {
|
||||
description = "Database fully qualified domain name"
|
||||
value = try(azurerm_postgresql_flexible_server.main[0].fqdn, null)
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "database_name" {
|
||||
description = "Database name"
|
||||
value = try(azurerm_postgresql_flexible_server_database.main[0].name, null)
|
||||
}
|
||||
|
||||
# AKS Outputs (if created)
|
||||
output "aks_cluster_name" {
|
||||
description = "AKS cluster name"
|
||||
value = try(azurerm_kubernetes_cluster.main[0].name, null)
|
||||
}
|
||||
|
||||
output "aks_fqdn" {
|
||||
description = "AKS cluster FQDN"
|
||||
value = try(azurerm_kubernetes_cluster.main[0].fqdn, null)
|
||||
}
|
||||
|
||||
output "log_analytics_workspace_id" {
|
||||
description = "Log Analytics Workspace ID"
|
||||
value = try(azurerm_log_analytics_workspace.main[0].workspace_id, null)
|
||||
}
|
||||
|
||||
# Export all outputs to environment variables file
|
||||
output "env_file_snippet" {
|
||||
description = "Environment variables snippet for .env file"
|
||||
value = <<-EOT
|
||||
# Azure Infrastructure Outputs (auto-generated)
|
||||
AZURE_SUBSCRIPTION_ID="${data.azurerm_subscription.current.subscription_id}"
|
||||
AZURE_TENANT_ID="${data.azurerm_client_config.current.tenant_id}"
|
||||
AZURE_RESOURCE_GROUP="${azurerm_resource_group.main.name}"
|
||||
AZURE_STORAGE_ACCOUNT="${azurerm_storage_account.app_data.name}"
|
||||
AZURE_KEY_VAULT_NAME="${azurerm_key_vault.main.name}"
|
||||
AZURE_KEY_VAULT_URI="${azurerm_key_vault.main.vault_uri}"
|
||||
CDN_STORAGE_ACCOUNT="${azurerm_storage_account.cdn_images.name}"
|
||||
CDN_BASE_URL_BLOB="https://${azurerm_storage_account.cdn_images.name}.blob.core.windows.net/${azurerm_storage_container.cdn_images.name}/"
|
||||
CDN_BASE_URL_CDN="https://${azurerm_cdn_endpoint.cdn_images.host_name}/${azurerm_storage_container.cdn_images.name}/"
|
||||
EOT
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
# Terraform outputs for The Order infrastructure
|
||||
|
||||
output "environment" {
|
||||
description = "Environment name"
|
||||
value = var.environment
|
||||
}
|
||||
|
||||
output "project_name" {
|
||||
description = "Project name"
|
||||
value = var.project_name
|
||||
}
|
||||
|
||||
output "azure_region" {
|
||||
description = "Azure region being used"
|
||||
value = var.azure_region
|
||||
}
|
||||
|
||||
output "region_abbreviation" {
|
||||
description = "Region abbreviation used in naming"
|
||||
value = local.region_short
|
||||
}
|
||||
|
||||
output "resource_group_name" {
|
||||
description = "Main resource group name (az-we-rg-dev-main)"
|
||||
value = azurerm_resource_group.main.name
|
||||
}
|
||||
|
||||
output "resource_group_id" {
|
||||
description = "Main resource group ID"
|
||||
value = azurerm_resource_group.main.id
|
||||
}
|
||||
|
||||
output "storage_account_name" {
|
||||
description = "Application data storage account name (azwesadevdata)"
|
||||
value = azurerm_storage_account.app_data.name
|
||||
}
|
||||
|
||||
output "storage_account_id" {
|
||||
description = "Application data storage account ID"
|
||||
value = azurerm_storage_account.app_data.id
|
||||
}
|
||||
|
||||
output "terraform_state_storage_account_name" {
|
||||
description = "Terraform state storage account name (azwesadevstate)"
|
||||
value = var.create_terraform_state_storage ? azurerm_storage_account.terraform_state[0].name : null
|
||||
}
|
||||
|
||||
output "terraform_state_resource_group_name" {
|
||||
description = "Terraform state resource group name (az-we-rg-dev-state)"
|
||||
value = var.create_terraform_state_rg ? azurerm_resource_group.terraform_state[0].name : null
|
||||
}
|
||||
|
||||
output "terraform_state_storage_container_name" {
|
||||
description = "Terraform state storage container name (if created)"
|
||||
value = var.create_terraform_state_storage ? azurerm_storage_container.terraform_state[0].name : null
|
||||
}
|
||||
|
||||
# Naming convention outputs for reference
|
||||
output "naming_convention" {
|
||||
description = "Naming convention pattern used"
|
||||
value = {
|
||||
pattern = "{provider}-{region}-{resource}-{env}-{purpose}"
|
||||
provider = local.provider
|
||||
region_abbrev = local.region_short
|
||||
env_abbrev = local.env_short
|
||||
examples = {
|
||||
resource_group = local.rg_name
|
||||
storage_account = local.sa_data_name
|
||||
key_vault = local.kv_name
|
||||
aks_cluster = local.aks_name
|
||||
container_registry = local.acr_name
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,254 +0,0 @@
|
||||
# Azure Policies for Cloud for Sovereignty
|
||||
# Ensures compliance with data residency and sovereignty requirements
|
||||
|
||||
variable "management_group_id" {
|
||||
description = "Root management group ID to assign policies"
|
||||
type = string
|
||||
default = "SOVEREIGN-ORDER-OF-HOSPITALLERS"
|
||||
}
|
||||
|
||||
# Configure Azure Provider
|
||||
provider "azurerm" {
|
||||
features {}
|
||||
}
|
||||
|
||||
# Policy: Allowed Locations (Non-US Commercial Regions Only)
|
||||
resource "azurerm_policy_definition" "allowed_locations" {
|
||||
name = "the-order-allowed-locations"
|
||||
policy_type = "Custom"
|
||||
mode = "All"
|
||||
display_name = "The Order - Allowed Locations (Non-US Commercial)"
|
||||
description = "Restricts resource deployment to non-US commercial Azure regions for data sovereignty"
|
||||
|
||||
metadata = jsonencode({
|
||||
category = "Location"
|
||||
})
|
||||
|
||||
policy_rule = jsonencode({
|
||||
if = {
|
||||
not = {
|
||||
field = "location"
|
||||
in = [
|
||||
"westeurope",
|
||||
"northeurope",
|
||||
"uksouth",
|
||||
"switzerlandnorth",
|
||||
"norwayeast",
|
||||
"francecentral",
|
||||
"germanywestcentral"
|
||||
]
|
||||
}
|
||||
}
|
||||
then = {
|
||||
effect = "Deny"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
# Policy: Deny US Regions
|
||||
resource "azurerm_policy_definition" "deny_us_regions" {
|
||||
name = "the-order-deny-us-regions"
|
||||
policy_type = "Custom"
|
||||
mode = "All"
|
||||
display_name = "The Order - Deny US Commercial and Government Regions"
|
||||
description = "Explicitly denies deployment to any US Commercial or Government regions"
|
||||
|
||||
metadata = jsonencode({
|
||||
category = "Location"
|
||||
})
|
||||
|
||||
policy_rule = jsonencode({
|
||||
if = {
|
||||
field = "location"
|
||||
like = "us*"
|
||||
}
|
||||
then = {
|
||||
effect = "Deny"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
# Policy: Require Data Residency Tags
|
||||
resource "azurerm_policy_definition" "require_data_residency_tag" {
|
||||
name = "the-order-require-data-residency-tag"
|
||||
policy_type = "Custom"
|
||||
mode = "Indexed"
|
||||
display_name = "The Order - Require Data Residency Tag"
|
||||
description = "Requires DataResidency tag on all resources for sovereignty tracking"
|
||||
|
||||
metadata = jsonencode({
|
||||
category = "Tags"
|
||||
})
|
||||
|
||||
policy_rule = jsonencode({
|
||||
if = {
|
||||
field = "[concat('tags[', parameters('tagName'), ']')]"
|
||||
exists = "false"
|
||||
}
|
||||
then = {
|
||||
effect = "Deny"
|
||||
}
|
||||
})
|
||||
|
||||
parameters = jsonencode({
|
||||
tagName = {
|
||||
type = "String"
|
||||
metadata = {
|
||||
displayName = "Tag Name"
|
||||
description = "Name of the tag, such as 'DataResidency'"
|
||||
}
|
||||
defaultValue = "DataResidency"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
# Policy: Require Encryption at Rest
|
||||
resource "azurerm_policy_definition" "require_encryption_at_rest" {
|
||||
name = "the-order-require-encryption-at-rest"
|
||||
policy_type = "Custom"
|
||||
mode = "All"
|
||||
display_name = "The Order - Require Encryption at Rest"
|
||||
description = "Ensures all storage accounts use encryption at rest with customer-managed keys"
|
||||
|
||||
metadata = jsonencode({
|
||||
category = "Security"
|
||||
})
|
||||
|
||||
policy_rule = jsonencode({
|
||||
if = {
|
||||
allOf = [
|
||||
{
|
||||
field = "type"
|
||||
equals = "Microsoft.Storage/storageAccounts"
|
||||
},
|
||||
{
|
||||
field = "Microsoft.Storage/storageAccounts/encryption.keySource"
|
||||
notEquals = "Microsoft.Keyvault"
|
||||
}
|
||||
]
|
||||
}
|
||||
then = {
|
||||
effect = "Deny"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
# Policy: Require Resource Tags
|
||||
resource "azurerm_policy_definition" "require_resource_tags" {
|
||||
name = "the-order-require-resource-tags"
|
||||
policy_type = "Custom"
|
||||
mode = "Indexed"
|
||||
display_name = "The Order - Require Resource Tags"
|
||||
description = "Requires specific tags on all resources for governance and cost management"
|
||||
|
||||
metadata = jsonencode({
|
||||
category = "Tags"
|
||||
})
|
||||
|
||||
policy_rule = jsonencode({
|
||||
if = {
|
||||
anyOf = [
|
||||
{
|
||||
field = "[concat('tags[', parameters('tagName1'), ']')]"
|
||||
exists = "false"
|
||||
},
|
||||
{
|
||||
field = "[concat('tags[', parameters('tagName2'), ']')]"
|
||||
exists = "false"
|
||||
},
|
||||
{
|
||||
field = "[concat('tags[', parameters('tagName3'), ']')]"
|
||||
exists = "false"
|
||||
}
|
||||
]
|
||||
}
|
||||
then = {
|
||||
effect = "Deny"
|
||||
}
|
||||
})
|
||||
|
||||
parameters = jsonencode({
|
||||
tagName1 = {
|
||||
type = "String"
|
||||
metadata = {
|
||||
displayName = "Tag Name 1"
|
||||
}
|
||||
defaultValue = "Environment"
|
||||
}
|
||||
tagName2 = {
|
||||
type = "String"
|
||||
metadata = {
|
||||
displayName = "Tag Name 2"
|
||||
}
|
||||
defaultValue = "Project"
|
||||
}
|
||||
tagName3 = {
|
||||
type = "String"
|
||||
metadata = {
|
||||
displayName = "Tag Name 3"
|
||||
}
|
||||
defaultValue = "DataClassification"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
# Policy Initiative: Cloud for Sovereignty Compliance
|
||||
resource "azurerm_policy_set_definition" "sovereignty_compliance" {
|
||||
name = "the-order-sovereignty-compliance"
|
||||
policy_type = "Custom"
|
||||
display_name = "The Order - Cloud for Sovereignty Compliance"
|
||||
description = "Policy initiative ensuring compliance with Cloud for Sovereignty requirements"
|
||||
|
||||
metadata = jsonencode({
|
||||
category = "Compliance"
|
||||
})
|
||||
|
||||
policy_definition_reference {
|
||||
policy_definition_id = azurerm_policy_definition.allowed_locations.id
|
||||
}
|
||||
|
||||
policy_definition_reference {
|
||||
policy_definition_id = azurerm_policy_definition.deny_us_regions.id
|
||||
}
|
||||
|
||||
policy_definition_reference {
|
||||
policy_definition_id = azurerm_policy_definition.require_data_residency_tag.id
|
||||
}
|
||||
|
||||
policy_definition_reference {
|
||||
policy_definition_id = azurerm_policy_definition.require_encryption_at_rest.id
|
||||
}
|
||||
|
||||
policy_definition_reference {
|
||||
policy_definition_id = azurerm_policy_definition.require_resource_tags.id
|
||||
}
|
||||
}
|
||||
|
||||
# Assign policy initiative to root management group
|
||||
resource "azurerm_management_group_policy_assignment" "sovereignty_compliance" {
|
||||
name = "sovereignty-compliance-assignment"
|
||||
management_group_id = var.management_group_id
|
||||
policy_definition_id = azurerm_policy_set_definition.sovereignty_compliance.id
|
||||
|
||||
identity {
|
||||
type = "SystemAssigned"
|
||||
}
|
||||
}
|
||||
|
||||
# Outputs
|
||||
output "policy_definitions" {
|
||||
description = "Created policy definitions"
|
||||
value = {
|
||||
allowed_locations = azurerm_policy_definition.allowed_locations.id
|
||||
deny_us_regions = azurerm_policy_definition.deny_us_regions.id
|
||||
require_data_residency_tag = azurerm_policy_definition.require_data_residency_tag.id
|
||||
require_encryption_at_rest = azurerm_policy_definition.require_encryption_at_rest.id
|
||||
require_resource_tags = azurerm_policy_definition.require_resource_tags.id
|
||||
}
|
||||
}
|
||||
|
||||
output "policy_initiative" {
|
||||
description = "Policy initiative ID"
|
||||
value = azurerm_policy_set_definition.sovereignty_compliance.id
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
# Variables for Sovereignty Policies
|
||||
|
||||
variable "management_group_id" {
|
||||
description = "Root management group ID to assign policies"
|
||||
type = string
|
||||
default = "SOVEREIGN-ORDER-OF-HOSPITALLERS"
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
# Terraform and Provider Version Constraints for Policies
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
|
||||
required_providers {
|
||||
azurerm = {
|
||||
source = "hashicorp/azurerm"
|
||||
version = "~> 3.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
# Resource Groups for The Order
|
||||
# Creates resource groups for each environment
|
||||
# Naming: az-we-rg-dev-main (provider-region-resource-env-purpose) or custom from variable
|
||||
|
||||
resource "azurerm_resource_group" "main" {
|
||||
name = var.resource_group_name != "" ? var.resource_group_name : local.rg_name
|
||||
location = var.azure_region
|
||||
|
||||
tags = merge(var.tags, {
|
||||
Purpose = "Main"
|
||||
})
|
||||
}
|
||||
|
||||
# Resource group for Terraform state (if using remote backend)
|
||||
resource "azurerm_resource_group" "terraform_state" {
|
||||
count = var.create_terraform_state_rg ? 1 : 0
|
||||
name = local.rg_state_name
|
||||
location = var.azure_region
|
||||
|
||||
tags = merge(local.common_tags, {
|
||||
Purpose = "TerraformState"
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
# Azure Storage Account for Terraform State Backend
|
||||
# This should be created first, then uncomment the backend block in versions.tf
|
||||
# Naming: azwesadevstate (provider+region+sa+env+purpose, alphanumeric only, max 24 chars)
|
||||
|
||||
resource "azurerm_storage_account" "terraform_state" {
|
||||
count = var.create_terraform_state_storage ? 1 : 0
|
||||
name = local.sa_state_name
|
||||
resource_group_name = azurerm_resource_group.terraform_state[0].name
|
||||
location = var.azure_region
|
||||
account_tier = "Standard"
|
||||
account_replication_type = "LRS"
|
||||
min_tls_version = "TLS1_2"
|
||||
|
||||
# Enable blob versioning and soft delete for state protection
|
||||
blob_properties {
|
||||
versioning_enabled = true
|
||||
delete_retention_policy {
|
||||
days = 30
|
||||
}
|
||||
}
|
||||
|
||||
tags = merge(local.common_tags, {
|
||||
Purpose = "TerraformState"
|
||||
})
|
||||
}
|
||||
|
||||
resource "azurerm_storage_container" "terraform_state" {
|
||||
count = var.create_terraform_state_storage ? 1 : 0
|
||||
name = "terraform-state"
|
||||
storage_account_name = azurerm_storage_account.terraform_state[0].name
|
||||
container_access_type = "private"
|
||||
}
|
||||
|
||||
# Storage Account for application data (object storage)
|
||||
# Naming: azwesadevdata (provider+region+sa+env+purpose, alphanumeric only, max 24 chars) or custom from variable
|
||||
resource "azurerm_storage_account" "app_data" {
|
||||
name = var.storage_account_name != "" ? var.storage_account_name : local.sa_data_name
|
||||
resource_group_name = azurerm_resource_group.main.name
|
||||
location = var.azure_region
|
||||
account_tier = "Standard"
|
||||
account_replication_type = var.environment == "prod" ? "GRS" : "LRS"
|
||||
min_tls_version = "TLS1_2"
|
||||
allow_blob_public_access = false
|
||||
|
||||
# Enable blob versioning for data protection
|
||||
blob_properties {
|
||||
versioning_enabled = true
|
||||
delete_retention_policy {
|
||||
days = var.environment == "prod" ? 90 : 30
|
||||
}
|
||||
container_delete_retention_policy {
|
||||
days = var.environment == "prod" ? 90 : 30
|
||||
}
|
||||
}
|
||||
|
||||
tags = merge(local.common_tags, {
|
||||
Purpose = "ApplicationData"
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
# Terraform Variables Example
|
||||
# Copy this file to terraform.tfvars and fill in your values
|
||||
# DO NOT commit terraform.tfvars to version control
|
||||
|
||||
# Azure Configuration
|
||||
azure_region = "westeurope" # No US regions allowed
|
||||
environment = "dev" # dev, stage, or prod
|
||||
project_name = "the-order"
|
||||
|
||||
# Azure Subscription
|
||||
subscription_id = "" # Your Azure subscription ID
|
||||
tenant_id = "" # Your Azure tenant ID
|
||||
|
||||
# Resource Naming
|
||||
resource_group_name = "the-order-rg-${environment}"
|
||||
storage_account_name = "theorder${environment}" # Must be globally unique, lowercase, alphanumeric
|
||||
key_vault_name = "the-order-kv-${environment}" # Must be globally unique
|
||||
|
||||
# Networking
|
||||
domain_name = "" # Optional: your domain name
|
||||
vnet_name = "the-order-vnet-${environment}"
|
||||
subnet_name = "the-order-subnet-${environment}"
|
||||
|
||||
# Database
|
||||
database_name = "theorder_${environment}"
|
||||
database_admin_user = "theorder_admin"
|
||||
# database_admin_password set via environment variable or Azure Key Vault
|
||||
|
||||
# Storage
|
||||
storage_account_tier = "Standard"
|
||||
storage_account_replication = "LRS" # LRS for dev, GRS for prod
|
||||
|
||||
# CDN (if using)
|
||||
cdn_profile_name = "theorder-cdn-${environment}"
|
||||
cdn_endpoint_name = "theorder-cdn-endpoint-${environment}"
|
||||
|
||||
# Kubernetes (AKS)
|
||||
aks_cluster_name = "the-order-aks-${environment}"
|
||||
aks_node_count = 2
|
||||
aks_vm_size = "Standard_B2s"
|
||||
|
||||
# Tags
|
||||
tags = {
|
||||
Environment = "${environment}"
|
||||
Project = "the-order"
|
||||
ManagedBy = "terraform"
|
||||
}
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
# Terraform variables for The Order infrastructure
|
||||
|
||||
variable "environment" {
|
||||
description = "Environment name (dev, stage, prod)"
|
||||
type = string
|
||||
validation {
|
||||
condition = contains(["dev", "stage", "prod"], var.environment)
|
||||
error_message = "Environment must be dev, stage, or prod."
|
||||
}
|
||||
}
|
||||
|
||||
variable "azure_region" {
|
||||
description = "Azure region (default: westeurope, no US regions allowed)"
|
||||
type = string
|
||||
default = "westeurope"
|
||||
|
||||
validation {
|
||||
condition = !can(regex("^us", var.azure_region))
|
||||
error_message = "US Commercial and Government regions are not allowed. Use European or other non-US regions."
|
||||
}
|
||||
|
||||
validation {
|
||||
condition = contains([
|
||||
"westeurope", "northeurope", "uksouth", "switzerlandnorth",
|
||||
"norwayeast", "francecentral", "germanywestcentral"
|
||||
], var.azure_region)
|
||||
error_message = "Region must be one of the supported non-US regions. See naming convention documentation."
|
||||
}
|
||||
}
|
||||
|
||||
variable "project_name" {
|
||||
description = "Project name"
|
||||
type = string
|
||||
default = "the-order"
|
||||
}
|
||||
|
||||
variable "domain_name" {
|
||||
description = "Domain name for the application"
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "subscription_id" {
|
||||
description = "Azure subscription ID"
|
||||
type = string
|
||||
default = ""
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "tenant_id" {
|
||||
description = "Azure tenant ID"
|
||||
type = string
|
||||
default = ""
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "client_id" {
|
||||
description = "Azure service principal client ID (optional, uses Azure CLI auth if not set)"
|
||||
type = string
|
||||
default = ""
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "client_secret" {
|
||||
description = "Azure service principal client secret (optional, uses Azure CLI auth if not set)"
|
||||
type = string
|
||||
default = ""
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "resource_group_name" {
|
||||
description = "Azure resource group name"
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "storage_account_name" {
|
||||
description = "Azure storage account name (must be globally unique)"
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "key_vault_name" {
|
||||
description = "Azure Key Vault name (must be globally unique)"
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "tags" {
|
||||
description = "Tags to apply to all resources"
|
||||
type = map(string)
|
||||
default = {
|
||||
Environment = "dev"
|
||||
Project = "the-order"
|
||||
ManagedBy = "terraform"
|
||||
}
|
||||
}
|
||||
|
||||
variable "cdn_profile_name" {
|
||||
description = "Azure CDN profile name"
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "cdn_endpoint_name" {
|
||||
description = "Azure CDN endpoint name"
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "enable_monitoring" {
|
||||
description = "Enable monitoring and observability"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "enable_logging" {
|
||||
description = "Enable centralized logging"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "create_terraform_state_rg" {
|
||||
description = "Create resource group for Terraform state storage"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "create_terraform_state_storage" {
|
||||
description = "Create storage account for Terraform state backend"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
# Terraform and Provider Version Constraints
|
||||
# Azure provider configuration - No US Commercial or Government regions
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
|
||||
required_providers {
|
||||
azurerm = {
|
||||
source = "hashicorp/azurerm"
|
||||
version = "~> 3.0"
|
||||
}
|
||||
random = {
|
||||
source = "hashicorp/random"
|
||||
version = "~> 3.0"
|
||||
}
|
||||
}
|
||||
|
||||
# Configure backend for state management
|
||||
# Uncomment and configure after creating Azure Storage Account
|
||||
# backend "azurerm" {
|
||||
# resource_group_name = "az-we-rg-dev-state"
|
||||
# storage_account_name = "azwesadevstate"
|
||||
# container_name = "terraform-state"
|
||||
# key = "terraform.tfstate"
|
||||
# }
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
/**
|
||||
* Well-Architected Framework Implementation
|
||||
* Main entry point for deploying Well-Architected infrastructure
|
||||
*/
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
required_providers {
|
||||
azurerm = {
|
||||
source = "hashicorp/azurerm"
|
||||
version = "~> 3.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Data sources
|
||||
data "azurerm_client_config" "current" {}
|
||||
data "azurerm_subscription" "current" {}
|
||||
|
||||
# Load environment variables
|
||||
locals {
|
||||
environment = var.environment != "" ? var.environment : (var.ENVIRONMENT != "" ? var.ENVIRONMENT : "dev")
|
||||
region = var.azure_region != "" ? var.azure_region : (var.AZURE_LOCATION != "" ? var.AZURE_LOCATION : "westeurope")
|
||||
|
||||
# Management group ID from environment or variable
|
||||
management_group_id = var.management_group_id != "" ? var.management_group_id : (var.AZURE_MANAGEMENT_GROUP_ID != "" ? var.AZURE_MANAGEMENT_GROUP_ID : "")
|
||||
}
|
||||
|
||||
# Resource Group
|
||||
resource "azurerm_resource_group" "well_architected" {
|
||||
name = "rg-well-architected-${local.environment}"
|
||||
location = local.region
|
||||
|
||||
tags = {
|
||||
Environment = local.environment
|
||||
Project = "the-order"
|
||||
CostCenter = "legal-services"
|
||||
Owner = "legal-team"
|
||||
DataClassification = "confidential"
|
||||
Sovereignty = "required"
|
||||
ManagedBy = "terraform"
|
||||
WellArchitected = "true"
|
||||
}
|
||||
}
|
||||
|
||||
# Well-Architected Module
|
||||
module "well_architected" {
|
||||
source = "../modules/well-architected"
|
||||
|
||||
name_prefix = "the-order"
|
||||
environment = local.environment
|
||||
region = local.region
|
||||
resource_group_name = azurerm_resource_group.well_architected.name
|
||||
resource_group_id = azurerm_resource_group.well_architected.id
|
||||
|
||||
# Cost Optimization
|
||||
enable_cost_management = true
|
||||
monthly_budget_amount = var.monthly_budget_amount
|
||||
budget_alert_emails = var.budget_alert_emails
|
||||
cost_export_storage_container_id = var.cost_export_storage_container_id
|
||||
|
||||
# Operational Excellence
|
||||
enable_automation = true
|
||||
|
||||
# Performance Efficiency
|
||||
enable_front_door = var.enable_front_door
|
||||
backend_host_header = var.backend_host_header
|
||||
backend_address = var.backend_address
|
||||
enable_redis_cache = true
|
||||
redis_capacity = local.environment == "production" ? 2 : 1
|
||||
redis_family = "C"
|
||||
|
||||
# Reliability
|
||||
enable_backup = true
|
||||
|
||||
# Security
|
||||
create_key_vault = false # Use existing Key Vault
|
||||
enable_defender = true
|
||||
enable_ddos_protection = true
|
||||
|
||||
# Cloud for Sovereignty
|
||||
enable_sovereignty_policies = true
|
||||
allowed_regions = var.allowed_regions
|
||||
management_group_id = local.management_group_id
|
||||
|
||||
tags = {
|
||||
WellArchitected = "true"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
variable "environment" {
|
||||
description = "Environment name (dev, staging, production)"
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "ENVIRONMENT" {
|
||||
description = "Environment name from environment variable"
|
||||
type = string
|
||||
default = ""
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "azure_region" {
|
||||
description = "Azure region"
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "AZURE_LOCATION" {
|
||||
description = "Azure location from environment variable"
|
||||
type = string
|
||||
default = ""
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "management_group_id" {
|
||||
description = "Management group ID"
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "AZURE_MANAGEMENT_GROUP_ID" {
|
||||
description = "Management group ID from environment variable"
|
||||
type = string
|
||||
default = ""
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "monthly_budget_amount" {
|
||||
description = "Monthly budget amount"
|
||||
type = number
|
||||
default = 10000
|
||||
}
|
||||
|
||||
variable "budget_alert_emails" {
|
||||
description = "Email addresses for budget alerts"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "cost_export_storage_container_id" {
|
||||
description = "Storage container ID for cost exports"
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "enable_front_door" {
|
||||
description = "Enable Azure Front Door"
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "backend_host_header" {
|
||||
description = "Backend host header for Front Door"
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "backend_address" {
|
||||
description = "Backend address for Front Door"
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "allowed_regions" {
|
||||
description = "List of allowed regions for data residency"
|
||||
type = list(string)
|
||||
default = [
|
||||
"westeurope",
|
||||
"northeurope",
|
||||
"uksouth",
|
||||
"switzerlandnorth",
|
||||
"norwayeast",
|
||||
"francecentral",
|
||||
"germanywestcentral"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user