Add new infra architecture

This commit is contained in:
Urtzi Alfaro
2026-01-19 11:55:17 +01:00
parent 21d35ea92b
commit 35f164f0cd
311 changed files with 13241 additions and 3700 deletions

View File

@@ -0,0 +1,168 @@
#!/usr/bin/env bash
# Apply all database security changes to Kubernetes cluster
set -e
NAMESPACE="bakery-ia"
echo "======================================"
echo "Bakery IA Database Security Deployment"
echo "======================================"
echo ""
echo "This script will apply all security changes to the cluster:"
echo " 1. Updated passwords"
echo " 2. TLS certificates for PostgreSQL and Redis"
echo " 3. Updated database deployments with TLS and PVCs"
echo " 4. PostgreSQL logging configuration"
echo " 5. pgcrypto extension"
echo ""
read -p "Press Enter to continue or Ctrl+C to cancel..."
echo ""
# ===== 1. Apply Secrets =====
echo "Step 1: Applying updated secrets..."
kubectl apply -f infrastructure/kubernetes/base/secrets.yaml
kubectl apply -f infrastructure/kubernetes/base/secrets/postgres-tls-secret.yaml
kubectl apply -f infrastructure/kubernetes/base/secrets/redis-tls-secret.yaml
echo "✓ Secrets applied"
echo ""
# ===== 2. Apply ConfigMaps =====
echo "Step 2: Applying ConfigMaps..."
kubectl apply -f infrastructure/kubernetes/base/configs/postgres-init-config.yaml
kubectl apply -f infrastructure/kubernetes/base/configmaps/postgres-logging-config.yaml
echo "✓ ConfigMaps applied"
echo ""
# ===== 3. Apply Database Deployments =====
echo "Step 3: Applying database deployments..."
kubectl apply -f infrastructure/kubernetes/base/components/databases/
echo "✓ Database deployments applied"
echo ""
# ===== 4. Wait for Rollout =====
echo "Step 4: Waiting for database pods to be ready..."
DBS=(
"auth-db"
"tenant-db"
"training-db"
"forecasting-db"
"sales-db"
"external-db"
"notification-db"
"inventory-db"
"recipes-db"
"suppliers-db"
"pos-db"
"orders-db"
"production-db"
"alert-processor-db"
"redis"
)
for db in "${DBS[@]}"; do
echo " Waiting for $db..."
kubectl rollout status deployment/$db -n $NAMESPACE --timeout=5m || echo " ⚠️ Warning: $db rollout may have issues"
done
echo "✓ All deployments rolled out"
echo ""
# ===== 5. Verify PVCs =====
echo "Step 5: Verifying PersistentVolumeClaims..."
kubectl get pvc -n $NAMESPACE
echo ""
# ===== 6. Test Database Connections =====
echo "Step 6: Testing database connectivity..."
# Test PostgreSQL with TLS
echo " Testing PostgreSQL (auth-db) with TLS..."
AUTH_POD=$(kubectl get pods -n $NAMESPACE -l app.kubernetes.io/name=auth-db -o jsonpath='{.items[0].metadata.name}')
if [ -n "$AUTH_POD" ]; then
kubectl exec -n $NAMESPACE "$AUTH_POD" -- \
sh -c 'psql -U $POSTGRES_USER -d $POSTGRES_DB -c "SELECT version();"' > /dev/null 2>&1 && \
echo " ✓ PostgreSQL connection successful" || \
echo " ⚠️ PostgreSQL connection test failed"
else
echo " ⚠️ auth-db pod not found"
fi
# Test Redis with TLS
echo " Testing Redis with TLS..."
REDIS_POD=$(kubectl get pods -n $NAMESPACE -l app.kubernetes.io/name=redis -o jsonpath='{.items[0].metadata.name}')
if [ -n "$REDIS_POD" ]; then
kubectl exec -n $NAMESPACE "$REDIS_POD" -- \
redis-cli -a $(kubectl get secret redis-secrets -n $NAMESPACE -o jsonpath='{.data.REDIS_PASSWORD}' | base64 -d) \
--tls --cert /tls/redis-cert.pem --key /tls/redis-key.pem --cacert /tls/ca-cert.pem \
PING > /dev/null 2>&1 && \
echo " ✓ Redis TLS connection successful" || \
echo " ⚠️ Redis TLS connection test failed (may need to restart services)"
else
echo " ⚠️ Redis pod not found"
fi
echo ""
# ===== 7. Verify TLS Certificates =====
echo "Step 7: Verifying TLS certificates are mounted..."
echo " Checking PostgreSQL TLS certs..."
if [ -n "$AUTH_POD" ]; then
kubectl exec -n $NAMESPACE "$AUTH_POD" -- ls -la /tls/ 2>/dev/null && \
echo " ✓ PostgreSQL TLS certificates mounted" || \
echo " ⚠️ PostgreSQL TLS certificates not found"
fi
echo " Checking Redis TLS certs..."
if [ -n "$REDIS_POD" ]; then
kubectl exec -n $NAMESPACE "$REDIS_POD" -- ls -la /tls/ 2>/dev/null && \
echo " ✓ Redis TLS certificates mounted" || \
echo " ⚠️ Redis TLS certificates not found"
fi
echo ""
# ===== 8. Display Summary =====
echo "======================================"
echo "Deployment Summary"
echo "======================================"
echo ""
echo "Database Pods:"
kubectl get pods -n $NAMESPACE -l app.kubernetes.io/component=database
echo ""
echo "PersistentVolumeClaims:"
kubectl get pvc -n $NAMESPACE | grep -E "NAME|db-pvc"
echo ""
echo "Secrets:"
kubectl get secrets -n $NAMESPACE | grep -E "NAME|database-secrets|redis-secrets|postgres-tls|redis-tls"
echo ""
echo "======================================"
echo "✓ Security Deployment Complete!"
echo "======================================"
echo ""
echo "Security improvements applied:"
echo " ✅ Strong 32-character passwords for all databases"
echo " ✅ TLS encryption for PostgreSQL connections"
echo " ✅ TLS encryption for Redis connections"
echo " ✅ Persistent storage (PVCs) for all databases"
echo " ✅ pgcrypto extension enabled for column-level encryption"
echo " ✅ PostgreSQL audit logging configured"
echo ""
echo "Next steps:"
echo " 1. Restart all services to pick up new database URLs with TLS"
echo " 2. Monitor logs for any connection issues"
echo " 3. Test application functionality end-to-end"
echo " 4. Review PostgreSQL logs: kubectl logs -n $NAMESPACE <db-pod>"
echo ""
echo "To create encrypted backups, run:"
echo " ./scripts/encrypted-backup.sh"
echo ""
echo "To enable Kubernetes secrets encryption (requires cluster recreate):"
echo " kind delete cluster --name bakery-ia-local"
echo " kind create cluster --config kind-config.yaml"
echo " kubectl apply -f infrastructure/kubernetes/base/namespace.yaml"
echo " ./scripts/apply-security-changes.sh"

View File

@@ -0,0 +1,161 @@
#!/bin/bash
# Database Backup Script for Bakery IA
# This script backs up all PostgreSQL databases in the Kubernetes cluster
# Designed to run on the VPS via cron
set -e
# Configuration
BACKUP_ROOT="/backups"
NAMESPACE="bakery-ia"
RETENTION_DAYS=7
DATE=$(date +%Y-%m-%d_%H-%M-%S)
BACKUP_DIR="${BACKUP_ROOT}/${DATE}"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Logging
log() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1"
}
log_error() {
echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1${NC}"
}
log_success() {
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] SUCCESS: $1${NC}"
}
log_warning() {
echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] WARNING: $1${NC}"
}
# Create backup directory
mkdir -p "$BACKUP_DIR"
log "Starting database backup to $BACKUP_DIR"
# Get all database pods
DB_PODS=$(kubectl get pods -n "$NAMESPACE" -l app.kubernetes.io/component=database -o jsonpath='{.items[*].metadata.name}')
if [ -z "$DB_PODS" ]; then
log_error "No database pods found in namespace $NAMESPACE"
exit 1
fi
log "Found database pods: $DB_PODS"
# Backup counter
SUCCESS_COUNT=0
FAILED_COUNT=0
FAILED_DBS=()
# Backup each database
for pod in $DB_PODS; do
log "Backing up database: $pod"
# Get database name from pod labels
DB_NAME=$(kubectl get pod "$pod" -n "$NAMESPACE" -o jsonpath='{.metadata.labels.app\.kubernetes\.io/name}')
if [ -z "$DB_NAME" ]; then
DB_NAME=$pod
fi
BACKUP_FILE="${BACKUP_DIR}/${DB_NAME}.sql"
# Perform backup
if kubectl exec -n "$NAMESPACE" "$pod" -- pg_dumpall -U postgres > "$BACKUP_FILE" 2>/dev/null; then
FILE_SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
log_success "Backed up $DB_NAME ($FILE_SIZE)"
((SUCCESS_COUNT++))
else
log_error "Failed to backup $DB_NAME"
FAILED_DBS+=("$DB_NAME")
((FAILED_COUNT++))
rm -f "$BACKUP_FILE" # Remove partial backup
fi
done
# Also backup Redis if present
REDIS_POD=$(kubectl get pods -n "$NAMESPACE" -l app.kubernetes.io/name=redis -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || echo "")
if [ -n "$REDIS_POD" ]; then
log "Backing up Redis: $REDIS_POD"
REDIS_BACKUP="${BACKUP_DIR}/redis.rdb"
if kubectl exec -n "$NAMESPACE" "$REDIS_POD" -- redis-cli --rdb /tmp/dump.rdb SAVE > /dev/null 2>&1 && \
kubectl cp "$NAMESPACE/$REDIS_POD:/tmp/dump.rdb" "$REDIS_BACKUP" > /dev/null 2>&1; then
FILE_SIZE=$(du -h "$REDIS_BACKUP" | cut -f1)
log_success "Backed up Redis ($FILE_SIZE)"
((SUCCESS_COUNT++))
else
log_warning "Failed to backup Redis (non-critical)"
fi
fi
# Create backup metadata
cat > "${BACKUP_DIR}/backup-info.txt" <<EOF
Backup Date: $(date)
Namespace: $NAMESPACE
Success Count: $SUCCESS_COUNT
Failed Count: $FAILED_COUNT
Failed Databases: ${FAILED_DBS[@]:-none}
Kubernetes Cluster: $(kubectl config current-context)
EOF
# Compress backup
log "Compressing backup..."
COMPRESSED_FILE="${BACKUP_ROOT}/backup-${DATE}.tar.gz"
if tar -czf "$COMPRESSED_FILE" -C "$BACKUP_ROOT" "$(basename "$BACKUP_DIR")"; then
COMPRESSED_SIZE=$(du -h "$COMPRESSED_FILE" | cut -f1)
log_success "Backup compressed: $COMPRESSED_FILE ($COMPRESSED_SIZE)"
# Remove uncompressed backup
rm -rf "$BACKUP_DIR"
else
log_error "Failed to compress backup"
exit 1
fi
# Cleanup old backups
log "Cleaning up backups older than $RETENTION_DAYS days..."
DELETED_COUNT=$(find "$BACKUP_ROOT" -name "backup-*.tar.gz" -mtime "+$RETENTION_DAYS" -delete -print | wc -l)
if [ "$DELETED_COUNT" -gt 0 ]; then
log "Deleted $DELETED_COUNT old backup(s)"
fi
# Calculate total backup size
TOTAL_SIZE=$(du -sh "$BACKUP_ROOT" | cut -f1)
# Summary
echo ""
log "========================================="
log "Backup Summary"
log "========================================="
log "Successful backups: $SUCCESS_COUNT"
log "Failed backups: $FAILED_COUNT"
log "Backup location: $COMPRESSED_FILE"
log "Backup size: $COMPRESSED_SIZE"
log "Total backup storage: $TOTAL_SIZE"
log "========================================="
if [ $FAILED_COUNT -gt 0 ]; then
log_error "Some backups failed: ${FAILED_DBS[*]}"
exit 1
fi
log_success "Backup completed successfully!"
# Optional: Send notification
# Uncomment and configure if you want email/slack notifications
# send_notification "Bakery IA Backup Completed" "Successfully backed up $SUCCESS_COUNT databases. Size: $COMPRESSED_SIZE"
exit 0

View File

@@ -0,0 +1,82 @@
#!/bin/bash
# Docker Cleanup Script for Local Kubernetes Development
# This script helps prevent disk space issues by cleaning up unused Docker resources
set -e
echo "🧹 Docker Cleanup Script for Bakery-IA Local Development"
echo "========================================================="
echo ""
# Check if we should run automatically or ask for confirmation
AUTO_MODE=${1:-""}
# Show current disk usage
echo "📊 Current Docker Disk Usage:"
docker system df
echo ""
# Check Kind node disk usage if cluster is running
if docker ps | grep -q "bakery-ia-local-control-plane"; then
echo "📊 Kind Node Disk Usage:"
docker exec bakery-ia-local-control-plane df -h / /var | grep -E "(Filesystem|overlay|/dev/vdb1)"
echo ""
fi
# Calculate reclaimable space
RECLAIMABLE=$(docker system df | grep "Images" | awk '{print $4}')
echo "💾 Estimated reclaimable space: $RECLAIMABLE"
echo ""
# Ask for confirmation unless in auto mode
if [ "$AUTO_MODE" != "--auto" ]; then
read -p "Do you want to proceed with cleanup? (y/n) " -n 1 -r
echo ""
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "❌ Cleanup cancelled"
exit 0
fi
fi
echo "🚀 Starting cleanup..."
echo ""
# Remove unused images (keep images from last 24 hours)
echo "1⃣ Removing unused Docker images..."
docker image prune -af --filter "until=24h" || true
echo ""
# Remove unused volumes
echo "2⃣ Removing unused Docker volumes..."
docker volume prune -f || true
echo ""
# Remove build cache
echo "3⃣ Removing build cache..."
docker builder prune -af || true
echo ""
# Show results
echo "✅ Cleanup completed!"
echo ""
echo "📊 Final Docker Disk Usage:"
docker system df
echo ""
# Check Kind node disk usage if cluster is running
if docker ps | grep -q "bakery-ia-local-control-plane"; then
echo "📊 Kind Node Disk Usage After Cleanup:"
docker exec bakery-ia-local-control-plane df -h / /var | grep -E "(Filesystem|overlay|/dev/vdb1)"
echo ""
# Warn if still above 80%
USAGE=$(docker exec bakery-ia-local-control-plane df -h /var | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$USAGE" -gt 80 ]; then
echo "⚠️ Warning: Disk usage is still above 80%. Consider:"
echo " - Deleting and recreating the Kind cluster"
echo " - Increasing Docker's disk allocation"
echo " - Running: docker system prune -a --volumes -f"
fi
fi
echo "🎉 All done!"

View File

@@ -0,0 +1,203 @@
#!/bin/bash
# Helper script to manually clean all service databases in Kubernetes
# This ensures databases are in a clean state before running migration generation
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration
NAMESPACE="${KUBE_NAMESPACE:-bakery-ia}"
# Parse command line arguments
CONFIRM=false
SPECIFIC_SERVICE=""
while [[ $# -gt 0 ]]; do
case $1 in
--namespace) NAMESPACE="$2"; shift 2 ;;
--service) SPECIFIC_SERVICE="$2"; shift 2 ;;
--yes) CONFIRM=true; shift ;;
-h|--help)
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Options:"
echo " --namespace NAME Use specific Kubernetes namespace (default: bakery-ia)"
echo " --service NAME Clean only specific service database"
echo " --yes Skip confirmation prompt"
echo ""
echo "Examples:"
echo " $0 # Clean all databases (with confirmation)"
echo " $0 --service auth --yes # Clean only auth database without confirmation"
exit 0
;;
*) echo "Unknown option: $1"; echo "Use --help for usage information"; exit 1 ;;
esac
done
# List of all services
SERVICES=(
"pos" "sales" "recipes" "training" "auth" "orders" "inventory"
"suppliers" "tenant" "notification" "alert-processor" "forecasting"
"external" "production"
)
# If specific service is provided, use only that
if [ -n "$SPECIFIC_SERVICE" ]; then
SERVICES=("$SPECIFIC_SERVICE")
fi
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE}Database Cleanup Script (K8s)${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
echo -e "${RED}⚠ WARNING: This will DROP ALL TABLES in the following databases:${NC}"
for service in "${SERVICES[@]}"; do
echo -e " - ${service}"
done
echo ""
if [ "$CONFIRM" = false ]; then
read -p "Are you sure you want to continue? (yes/no) " -r
echo
if [[ ! $REPLY =~ ^yes$ ]]; then
echo -e "${YELLOW}Aborted.${NC}"
exit 0
fi
fi
SUCCESS_COUNT=0
FAILED_COUNT=0
FAILED_SERVICES=()
# Function to get a running pod for a service
get_running_pod() {
local service=$1
local pod_name=""
local selectors=(
"app.kubernetes.io/name=${service}-service,app.kubernetes.io/component=microservice"
"app.kubernetes.io/name=${service}-service,app.kubernetes.io/component=worker"
"app.kubernetes.io/name=${service}-service"
)
for selector in "${selectors[@]}"; do
pod_name=$(kubectl get pods -n "$NAMESPACE" -l "$selector" --field-selector=status.phase=Running -o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
if [ -n "$pod_name" ]; then
echo "$pod_name"
return 0
fi
done
echo ""
return 1
}
echo ""
echo -e "${BLUE}Starting database cleanup...${NC}"
echo ""
for service in "${SERVICES[@]}"; do
echo -e "${BLUE}----------------------------------------${NC}"
echo -e "${BLUE}Cleaning: $service${NC}"
echo -e "${BLUE}----------------------------------------${NC}"
# Find pod
POD_NAME=$(get_running_pod "$service")
if [ -z "$POD_NAME" ]; then
echo -e "${RED}✗ No running pod found, skipping...${NC}"
FAILED_COUNT=$((FAILED_COUNT + 1))
FAILED_SERVICES+=("$service (pod not found)")
continue
fi
echo -e "${GREEN}✓ Found pod: $POD_NAME${NC}"
# Get database URL environment variable name
db_env_var=$(echo "$service" | tr '[:lower:]-' '[:upper:]_')_DATABASE_URL
CONTAINER="${service}-service"
# Drop all tables
CLEANUP_RESULT=$(kubectl exec -n "$NAMESPACE" "$POD_NAME" -c "$CONTAINER" -- sh -c "cd /app && PYTHONPATH=/app:/app/shared:\$PYTHONPATH python3 << 'EOFPYTHON'
import asyncio
import os
from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy import text
async def cleanup_database():
try:
engine = create_async_engine(os.getenv('$db_env_var'))
# Get list of tables before cleanup
async with engine.connect() as conn:
result = await conn.execute(text(\"\"\"
SELECT COUNT(*)
FROM pg_tables
WHERE schemaname = 'public'
\"\"\"))
table_count_before = result.scalar()
# Drop and recreate public schema
async with engine.begin() as conn:
await conn.execute(text('DROP SCHEMA IF EXISTS public CASCADE'))
await conn.execute(text('CREATE SCHEMA public'))
await conn.execute(text('GRANT ALL ON SCHEMA public TO PUBLIC'))
# Verify cleanup
async with engine.connect() as conn:
result = await conn.execute(text(\"\"\"
SELECT COUNT(*)
FROM pg_tables
WHERE schemaname = 'public'
\"\"\"))
table_count_after = result.scalar()
await engine.dispose()
print(f'SUCCESS: Dropped {table_count_before} tables, {table_count_after} remaining')
return 0
except Exception as e:
print(f'ERROR: {str(e)}')
return 1
exit(asyncio.run(cleanup_database()))
EOFPYTHON
" 2>&1)
if echo "$CLEANUP_RESULT" | grep -q "SUCCESS"; then
echo -e "${GREEN}✓ Database cleaned successfully${NC}"
echo -e "${BLUE} $CLEANUP_RESULT${NC}"
SUCCESS_COUNT=$((SUCCESS_COUNT + 1))
else
echo -e "${RED}✗ Database cleanup failed${NC}"
echo -e "${YELLOW}Error details: $CLEANUP_RESULT${NC}"
FAILED_COUNT=$((FAILED_COUNT + 1))
FAILED_SERVICES+=("$service (cleanup failed)")
fi
echo ""
done
# Summary
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE}Summary${NC}"
echo -e "${BLUE}========================================${NC}"
echo -e "${GREEN}✓ Successfully cleaned: $SUCCESS_COUNT databases${NC}"
echo -e "${RED}✗ Failed: $FAILED_COUNT databases${NC}"
if [ "$FAILED_COUNT" -gt 0 ]; then
echo ""
echo -e "${RED}Failed services:${NC}"
for failed_service in "${FAILED_SERVICES[@]}"; do
echo -e "${RED} - $failed_service${NC}"
done
fi
echo ""
if [ "$SUCCESS_COUNT" -gt 0 ]; then
echo -e "${GREEN}Databases are now clean and ready for migration generation!${NC}"
echo -e "${YELLOW}Next step: Run ./regenerate_migrations_k8s.sh${NC}"
fi

View File

@@ -0,0 +1,190 @@
#!/bin/bash
# Production Deployment Script for MicroK8s
# This script helps deploy Bakery IA to a MicroK8s cluster
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo -e "${GREEN}========================================${NC}"
echo -e "${GREEN}Bakery IA - Production Deployment${NC}"
echo -e "${GREEN}========================================${NC}"
echo ""
# Configuration
NAMESPACE="bakery-ia"
KUSTOMIZE_PATH="infrastructure/environments/prod/k8s-manifests"
# Check if kubectl is available
if ! command -v kubectl &> /dev/null; then
echo -e "${RED}Error: kubectl not found. Please install kubectl or setup microk8s alias.${NC}"
exit 1
fi
# Function to check if cluster is accessible
check_cluster() {
echo -e "${YELLOW}Checking cluster connectivity...${NC}"
if ! kubectl cluster-info &> /dev/null; then
echo -e "${RED}Error: Cannot connect to Kubernetes cluster.${NC}"
echo "Please ensure your kubeconfig is set correctly."
exit 1
fi
echo -e "${GREEN}✓ Cluster connection successful${NC}"
echo ""
}
# Function to check required addons
check_addons() {
echo -e "${YELLOW}Checking required MicroK8s addons...${NC}"
# Check if this is MicroK8s
if command -v microk8s &> /dev/null; then
REQUIRED_ADDONS=("dns" "hostpath-storage" "ingress" "cert-manager" "metrics-server")
for addon in "${REQUIRED_ADDONS[@]}"; do
if microk8s status | grep -q "$addon: enabled"; then
echo -e "${GREEN}$addon enabled${NC}"
else
echo -e "${RED}$addon not enabled${NC}"
echo -e "${YELLOW}Enable with: microk8s enable $addon${NC}"
exit 1
fi
done
else
echo -e "${YELLOW}Not running on MicroK8s. Skipping addon check.${NC}"
fi
echo ""
}
# Function to create namespace
create_namespace() {
echo -e "${YELLOW}Creating namespace...${NC}"
if kubectl get namespace $NAMESPACE &> /dev/null; then
echo -e "${GREEN}✓ Namespace $NAMESPACE already exists${NC}"
else
kubectl create namespace $NAMESPACE
echo -e "${GREEN}✓ Namespace $NAMESPACE created${NC}"
fi
echo ""
}
# Function to apply secrets
apply_secrets() {
echo -e "${YELLOW}Applying secrets...${NC}"
echo -e "${RED}WARNING: Ensure production secrets are updated before deployment!${NC}"
read -p "Have you updated production secrets? (yes/no): " confirm
if [ "$confirm" != "yes" ]; then
echo -e "${RED}Deployment cancelled. Please update secrets first.${NC}"
exit 1
fi
kubectl apply -f infrastructure/kubernetes/base/secrets.yaml
kubectl apply -f infrastructure/kubernetes/base/secrets/postgres-tls-secret.yaml
kubectl apply -f infrastructure/kubernetes/base/secrets/redis-tls-secret.yaml
kubectl apply -f infrastructure/kubernetes/base/secrets/demo-internal-api-key-secret.yaml
echo -e "${GREEN}✓ Secrets applied${NC}"
echo ""
}
# Function to apply kustomization
deploy_application() {
echo -e "${YELLOW}Deploying application...${NC}"
kubectl apply -k $KUSTOMIZE_PATH
echo -e "${GREEN}✓ Application deployed${NC}"
echo ""
}
# Function to wait for deployments
wait_for_deployments() {
echo -e "${YELLOW}Waiting for deployments to be ready...${NC}"
echo "This may take several minutes..."
# Wait for all deployments
kubectl wait --for=condition=available --timeout=600s \
deployment --all -n $NAMESPACE
echo -e "${GREEN}✓ All deployments are ready${NC}"
echo ""
}
# Function to check deployment status
check_status() {
echo -e "${YELLOW}Deployment Status:${NC}"
echo ""
echo "Pods:"
kubectl get pods -n $NAMESPACE
echo ""
echo "Services:"
kubectl get svc -n $NAMESPACE
echo ""
echo "Ingress:"
kubectl get ingress -n $NAMESPACE
echo ""
echo "Persistent Volume Claims:"
kubectl get pvc -n $NAMESPACE
echo ""
echo "Certificates:"
kubectl get certificate -n $NAMESPACE
echo ""
}
# Function to show access information
show_access_info() {
echo -e "${GREEN}========================================${NC}"
echo -e "${GREEN}Deployment Complete!${NC}"
echo -e "${GREEN}========================================${NC}"
echo ""
echo "Access your application at:"
# Get ingress hosts
HOSTS=$(kubectl get ingress bakery-ingress-prod -n $NAMESPACE -o jsonpath='{.spec.rules[*].host}' 2>/dev/null || echo "")
if [ -n "$HOSTS" ]; then
for host in $HOSTS; do
echo " https://$host"
done
else
echo " Configure your domain in prod-ingress.yaml"
fi
echo ""
echo "Useful commands:"
echo " View logs: kubectl logs -f deployment/gateway -n $NAMESPACE"
echo " Check pods: kubectl get pods -n $NAMESPACE"
echo " Check events: kubectl get events -n $NAMESPACE --sort-by='.lastTimestamp'"
echo " Scale: kubectl scale deployment/gateway --replicas=5 -n $NAMESPACE"
echo ""
}
# Main deployment flow
main() {
check_cluster
check_addons
create_namespace
apply_secrets
deploy_application
echo -e "${YELLOW}Do you want to wait for deployments to be ready? (yes/no):${NC}"
read -p "> " wait_confirm
if [ "$wait_confirm" = "yes" ]; then
wait_for_deployments
fi
check_status
show_access_info
}
# Run main function
main

View File

@@ -0,0 +1,82 @@
#!/usr/bin/env bash
# Encrypted PostgreSQL Backup Script
# Creates GPG-encrypted backups of all databases
set -e
BACKUP_DIR="${BACKUP_DIR:-/backups}"
BACKUP_DATE=$(date +%Y%m%d-%H%M%S)
GPG_RECIPIENT="${GPG_RECIPIENT:-backup@bakery-ia.com}"
NAMESPACE="${NAMESPACE:-bakery-ia}"
# Database list
DATABASES=(
"auth-db"
"tenant-db"
"training-db"
"forecasting-db"
"sales-db"
"external-db"
"notification-db"
"inventory-db"
"recipes-db"
"suppliers-db"
"pos-db"
"orders-db"
"production-db"
"alert-processor-db"
)
echo "Starting encrypted backup process..."
echo "Backup date: $BACKUP_DATE"
echo "Backup directory: $BACKUP_DIR"
echo "Namespace: $NAMESPACE"
echo ""
# Create backup directory if it doesn't exist
mkdir -p "$BACKUP_DIR"
for db in "${DATABASES[@]}"; do
echo "Backing up $db..."
# Get pod name
POD=$(kubectl get pods -n "$NAMESPACE" -l "app.kubernetes.io/name=$db" -o jsonpath='{.items[0].metadata.name}')
if [ -z "$POD" ]; then
echo " ⚠️ Warning: Pod not found for $db, skipping"
continue
fi
# Extract database name from environment
DB_NAME=$(kubectl exec -n "$NAMESPACE" "$POD" -- sh -c 'echo $POSTGRES_DB')
DB_USER=$(kubectl exec -n "$NAMESPACE" "$POD" -- sh -c 'echo $POSTGRES_USER')
# Create backup file name
BACKUP_FILE="$BACKUP_DIR/${db}_${DB_NAME}_${BACKUP_DATE}.sql.gz.gpg"
# Perform backup with pg_dump, compress with gzip, encrypt with GPG
kubectl exec -n "$NAMESPACE" "$POD" -- \
sh -c "pg_dump -U $DB_USER -d $DB_NAME" | \
gzip | \
gpg --encrypt --recipient "$GPG_RECIPIENT" --trust-model always > "$BACKUP_FILE"
# Get file size
SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
echo " ✓ Backup complete: $BACKUP_FILE ($SIZE)"
done
echo ""
echo "===================="
echo "✓ Backup process completed!"
echo ""
echo "Total backups created: ${#DATABASES[@]}"
echo "Backup location: $BACKUP_DIR"
echo "Backup date: $BACKUP_DATE"
echo ""
echo "To decrypt a backup:"
echo " gpg --decrypt backup_file.sql.gz.gpg | gunzip > backup.sql"
echo ""
echo "To restore a backup:"
echo " gpg --decrypt backup_file.sql.gz.gpg | gunzip | psql -U user -d database"

View File

@@ -0,0 +1,60 @@
#!/bin/bash
# Fix OTEL endpoint configuration in all service manifests
# This script replaces hardcoded OTEL_EXPORTER_OTLP_ENDPOINT values
# with references to the central bakery-config ConfigMap
set -e
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m'
echo -e "${BLUE}Fixing OTEL endpoint configuration in all services...${NC}"
echo ""
# Find all service YAML files
SERVICE_FILES=$(find infrastructure/kubernetes/base/components -name "*-service.yaml")
for file in $SERVICE_FILES; do
# Check if file contains hardcoded OTEL_EXPORTER_OTLP_ENDPOINT
if grep -q "name: OTEL_EXPORTER_OTLP_ENDPOINT" "$file"; then
# Check if it's already using configMapKeyRef
if grep -A 3 "name: OTEL_EXPORTER_OTLP_ENDPOINT" "$file" | grep -q "configMapKeyRef"; then
echo -e "${GREEN}$file already using ConfigMap${NC}"
else
echo -e "${BLUE}→ Fixing $file${NC}"
# Create a temporary file
tmp_file=$(mktemp)
# Process the file
awk '
/name: OTEL_EXPORTER_OTLP_ENDPOINT/ {
print $0
# Read and skip the next line (value line)
getline
# Output the configMapKeyRef instead
print " valueFrom:"
print " configMapKeyRef:"
print " name: bakery-config"
print " key: OTEL_EXPORTER_OTLP_ENDPOINT"
next
}
{ print }
' "$file" > "$tmp_file"
# Replace original file
mv "$tmp_file" "$file"
echo -e "${GREEN} ✓ Fixed${NC}"
fi
fi
done
echo ""
echo -e "${GREEN}✓ All service files processed!${NC}"
echo ""
echo "Next steps:"
echo "1. Review changes: git diff infrastructure/kubernetes/base/components"
echo "2. Apply changes: kubectl apply -k infrastructure/environments/dev/k8s-manifests"
echo "3. Restart services: kubectl rollout restart deployment -n bakery-ia --all"

View File

@@ -0,0 +1,58 @@
#!/usr/bin/env bash
# Script to generate cryptographically secure passwords for all databases
# Generates 32-character random passwords using openssl
set -e
echo "Generating secure passwords for all databases..."
echo ""
# Generate password function
generate_password() {
openssl rand -base64 32 | tr -d "=+/" | cut -c1-32
}
# Generate passwords for all services
SERVICES=(
"AUTH_DB_PASSWORD"
"TRAINING_DB_PASSWORD"
"FORECASTING_DB_PASSWORD"
"SALES_DB_PASSWORD"
"EXTERNAL_DB_PASSWORD"
"TENANT_DB_PASSWORD"
"NOTIFICATION_DB_PASSWORD"
"ALERT_PROCESSOR_DB_PASSWORD"
"INVENTORY_DB_PASSWORD"
"RECIPES_DB_PASSWORD"
"SUPPLIERS_DB_PASSWORD"
"POS_DB_PASSWORD"
"ORDERS_DB_PASSWORD"
"PRODUCTION_DB_PASSWORD"
"REDIS_PASSWORD"
)
echo "Generated Passwords:"
echo "===================="
echo ""
count=0
for service in "${SERVICES[@]}"; do
password=$(generate_password)
echo "$service=$password"
count=$((count + 1))
done
echo ""
echo "===================="
echo ""
echo "Passwords generated successfully!"
echo "Total: $count passwords"
echo ""
echo "Next steps:"
echo "1. Update .env file with these passwords"
echo "2. Update infrastructure/kubernetes/base/secrets.yaml with base64-encoded passwords"
echo "3. Apply new secrets to Kubernetes cluster"
echo ""
echo "To base64 encode a password:"
echo " echo -n 'password' | base64"

View File

@@ -0,0 +1,141 @@
#!/bin/bash
# Generate Test Traffic to Services
# This script generates API calls to verify telemetry data collection
set -e
NAMESPACE="bakery-ia"
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
NC='\033[0m'
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BLUE} Generating Test Traffic for SigNoz Verification${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
# Check if ingress is accessible
echo -e "${BLUE}Step 1: Verifying Gateway Access${NC}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
GATEWAY_POD=$(kubectl get pods -n $NAMESPACE -l app=gateway --field-selector=status.phase=Running -o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
if [[ -z "$GATEWAY_POD" ]]; then
echo -e "${YELLOW}⚠ Gateway pod not running. Starting port-forward...${NC}"
# Port forward in background
kubectl port-forward -n $NAMESPACE svc/gateway-service 8000:8000 &
PORT_FORWARD_PID=$!
sleep 3
API_URL="http://localhost:8000"
else
echo -e "${GREEN}✓ Gateway is running: $GATEWAY_POD${NC}"
# Use internal service
API_URL="http://gateway-service.$NAMESPACE.svc.cluster.local:8000"
fi
echo ""
# Function to make API call from inside cluster
make_request() {
local endpoint=$1
local description=$2
echo -e "${BLUE}→ Testing: $description${NC}"
echo " Endpoint: $endpoint"
if [[ -n "$GATEWAY_POD" ]]; then
# Make request from inside the gateway pod
RESPONSE=$(kubectl exec -n $NAMESPACE $GATEWAY_POD -- curl -s -w "\nHTTP_CODE:%{http_code}" "$API_URL$endpoint" 2>/dev/null || echo "FAILED")
else
# Make request from localhost
RESPONSE=$(curl -s -w "\nHTTP_CODE:%{http_code}" "$API_URL$endpoint" 2>/dev/null || echo "FAILED")
fi
if [[ "$RESPONSE" == "FAILED" ]]; then
echo -e " ${YELLOW}⚠ Request failed${NC}"
else
HTTP_CODE=$(echo "$RESPONSE" | grep "HTTP_CODE" | cut -d: -f2)
if [[ "$HTTP_CODE" == "200" ]] || [[ "$HTTP_CODE" == "401" ]] || [[ "$HTTP_CODE" == "404" ]]; then
echo -e " ${GREEN}✓ Response received (HTTP $HTTP_CODE)${NC}"
else
echo -e " ${YELLOW}⚠ Unexpected response (HTTP $HTTP_CODE)${NC}"
fi
fi
echo ""
sleep 1
}
# Generate traffic to various endpoints
echo -e "${BLUE}Step 2: Generating Traffic to Services${NC}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
# Health checks (should generate traces)
make_request "/health" "Gateway Health Check"
make_request "/api/health" "API Health Check"
# Auth service endpoints
make_request "/api/auth/health" "Auth Service Health"
# Tenant service endpoints
make_request "/api/tenants/health" "Tenant Service Health"
# Inventory service endpoints
make_request "/api/inventory/health" "Inventory Service Health"
# Orders service endpoints
make_request "/api/orders/health" "Orders Service Health"
# Forecasting service endpoints
make_request "/api/forecasting/health" "Forecasting Service Health"
echo -e "${BLUE}Step 3: Checking Service Logs for Telemetry${NC}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
# Check a few service pods for tracing logs
SERVICES=("auth-service" "inventory-service" "gateway")
for service in "${SERVICES[@]}"; do
POD=$(kubectl get pods -n $NAMESPACE -l app=$service --field-selector=status.phase=Running -o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
if [[ -n "$POD" ]]; then
echo -e "${BLUE}Checking $service ($POD)...${NC}"
TRACING_LOG=$(kubectl logs -n $NAMESPACE $POD --tail=100 2>/dev/null | grep -i "tracing\|otel" | head -n 2 || echo "")
if [[ -n "$TRACING_LOG" ]]; then
echo -e "${GREEN}✓ Tracing configured:${NC}"
echo "$TRACING_LOG" | sed 's/^/ /'
else
echo -e "${YELLOW}⚠ No tracing logs found${NC}"
fi
echo ""
fi
done
# Wait for data to be processed
echo -e "${BLUE}Step 4: Waiting for Data Processing${NC}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Waiting 30 seconds for telemetry data to be processed..."
for i in {30..1}; do
echo -ne "\r ${i} seconds remaining..."
sleep 1
done
echo -e "\n"
# Cleanup port-forward if started
if [[ -n "$PORT_FORWARD_PID" ]]; then
kill $PORT_FORWARD_PID 2>/dev/null || true
fi
echo -e "${GREEN}✓ Test traffic generation complete!${NC}"
echo ""
echo -e "${BLUE}Next Steps:${NC}"
echo "1. Run the verification script to check for collected data:"
echo " ./infrastructure/helm/verify-signoz-telemetry.sh"
echo ""
echo "2. Access SigNoz UI to visualize the data:"
echo " https://monitoring.bakery-ia.local"
echo " or"
echo " kubectl port-forward -n bakery-ia svc/signoz 3301:8080"
echo " Then go to: http://localhost:3301"
echo ""
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"

View File

@@ -0,0 +1,129 @@
#!/bin/bash
# Script to generate a comprehensive test report for the subscription creation flow
# This script checks all components and generates a detailed report
echo "📊 Generating Subscription Creation Flow Test Report"
echo "===================================================="
echo "Report generated on: $(date)"
echo ""
# Test 1: Check if database migration was applied
echo "🔍 Test 1: Database Migration Check"
echo "-----------------------------------"
POD_NAME=$(kubectl get pods -n bakery-ia -l app=auth-service -o jsonpath='{.items[0].metadata.name}')
MIGRATION_STATUS=$(kubectl exec -n bakery-ia $POD_NAME -- psql -U auth_user -d auth_db -c "SELECT version_num FROM alembic_version" -t -A)
if [[ "$MIGRATION_STATUS" == "20260113_add_payment_columns" ]]; then
echo "✅ PASS: Database migration '20260113_add_payment_columns' is applied"
else
echo "❌ FAIL: Database migration not found. Current version: $MIGRATION_STATUS"
fi
echo ""
# Test 2: Check if payment columns exist in users table
echo "🔍 Test 2: Payment Columns in Users Table"
echo "------------------------------------------"
COLUMNS=$(kubectl exec -n bakery-ia $POD_NAME -- psql -U auth_user -d auth_db -c "\d users" -t -A | grep -E "payment_customer_id|default_payment_method_id")
if [[ -n "$COLUMNS" ]]; then
echo "✅ PASS: Payment columns found in users table"
echo " Found columns:"
echo " $COLUMNS" | sed 's/^/ /'
else
echo "❌ FAIL: Payment columns not found in users table"
fi
echo ""
# Test 3: Check if gateway route exists
echo "🔍 Test 3: Gateway Route Configuration"
echo "--------------------------------------"
GATEWAY_POD=$(kubectl get pods -n bakery-ia -l app=gateway -o jsonpath='{.items[0].metadata.name}')
ROUTE_CHECK=$(kubectl exec -n bakery-ia $GATEWAY_POD -- grep -c "create-for-registration" /app/app/routes/subscription.py)
if [[ "$ROUTE_CHECK" -gt 0 ]]; then
echo "✅ PASS: Gateway route for 'create-for-registration' is configured"
else
echo "❌ FAIL: Gateway route for 'create-for-registration' not found"
fi
echo ""
# Test 4: Check if tenant service endpoint exists
echo "🔍 Test 4: Tenant Service Endpoint"
echo "-----------------------------------"
TENANT_POD=$(kubectl get pods -n bakery-ia -l app=tenant-service -o jsonpath='{.items[0].metadata.name}')
ENDPOINT_CHECK=$(kubectl exec -n bakery-ia $TENANT_POD -- grep -c "create-for-registration" /app/app/api/subscription.py)
if [[ "$ENDPOINT_CHECK" -gt 0 ]]; then
echo "✅ PASS: Tenant service endpoint 'create-for-registration' is configured"
else
echo "❌ FAIL: Tenant service endpoint 'create-for-registration' not found"
fi
echo ""
# Test 5: Test user registration (create a test user)
echo "🔍 Test 5: User Registration Test"
echo "--------------------------------"
TEST_EMAIL="test_$(date +%Y%m%d%H%M%S)@example.com"
REGISTRATION_RESPONSE=$(curl -X POST "https://bakery-ia.local/api/v1/auth/register-with-subscription" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d "{\"email\":\"$TEST_EMAIL\",\"password\":\"SecurePassword123!\",\"full_name\":\"Test User\",\"subscription_plan\":\"basic\",\"payment_method_id\":\"pm_test123\"}" \
-k -s)
if echo "$REGISTRATION_RESPONSE" | grep -q "access_token"; then
echo "✅ PASS: User registration successful"
USER_ID=$(echo "$REGISTRATION_RESPONSE" | python3 -c "import sys, json; print(json.load(sys.stdin)['user']['id'])")
echo " Created user ID: $USER_ID"
else
echo "❌ FAIL: User registration failed"
echo " Response: $REGISTRATION_RESPONSE"
fi
echo ""
# Test 6: Check if user has payment fields
echo "🔍 Test 6: User Payment Fields"
echo "------------------------------"
if [[ -n "$USER_ID" ]]; then
USER_DATA=$(kubectl exec -n bakery-ia $POD_NAME -- psql -U auth_user -d auth_db -c "SELECT payment_customer_id, default_payment_method_id FROM users WHERE id = '$USER_ID'" -t -A)
if [[ -n "$USER_DATA" ]]; then
echo "✅ PASS: User has payment fields in database"
echo " Payment data: $USER_DATA"
else
echo "❌ FAIL: User payment fields not found"
fi
else
echo "⚠️ SKIP: User ID not available from previous test"
fi
echo ""
# Test 7: Check subscription creation in onboarding progress
echo "🔍 Test 7: Subscription in Onboarding Progress"
echo "---------------------------------------------"
if [[ -n "$USER_ID" ]]; then
# This would require authentication, so we'll skip for now
echo "⚠️ SKIP: Requires authentication (would need to implement token handling)"
else
echo "⚠️ SKIP: User ID not available from previous test"
fi
echo ""
# Summary
echo "📋 Test Summary"
echo "==============="
echo "The subscription creation flow test report has been generated."
echo ""
echo "Components tested:"
echo " 1. Database migration"
echo " 2. Payment columns in users table"
echo " 3. Gateway route configuration"
echo " 4. Tenant service endpoint"
echo " 5. User registration"
echo " 6. User payment fields"
echo " 7. Subscription in onboarding progress"
echo ""
echo "For a complete integration test, run:"
echo " ./scripts/run_subscription_integration_test.sh"
echo ""
echo "🎉 Report generation completed!"

View File

@@ -0,0 +1,369 @@
#!/bin/bash
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored output
print_status() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Function to wait for pods with retry logic
wait_for_pods() {
local namespace=$1
local selector=$2
local timeout=$3
local max_retries=30
local retry_count=0
print_status "Waiting for pods with selector '$selector' in namespace '$namespace'..."
while [ $retry_count -lt $max_retries ]; do
# Check if any pods exist first
if kubectl get pods -n "$namespace" --selector="$selector" 2>/dev/null | grep -v "No resources found" | grep -v "NAME" > /dev/null; then
# Pods exist, now wait for them to be ready
if kubectl wait --namespace "$namespace" \
--for=condition=ready pod \
--selector="$selector" \
--timeout="${timeout}s" 2>/dev/null; then
print_success "Pods are ready"
return 0
fi
fi
retry_count=$((retry_count + 1))
print_status "Waiting for pods to be created... (attempt $retry_count/$max_retries)"
sleep 5
done
print_error "Timed out waiting for pods after $((max_retries * 5)) seconds"
return 1
}
# Function to handle cleanup
cleanup() {
print_status "Starting cleanup process..."
# Delete Kubernetes namespace with timeout
print_status "Deleting namespace bakery-ia..."
if kubectl get namespace bakery-ia &>/dev/null; then
kubectl delete namespace bakery-ia 2>/dev/null &
PID=$!
sleep 2
if ps -p $PID &>/dev/null; then
print_warning "kubectl delete namespace command taking too long, forcing termination..."
kill $PID 2>/dev/null
fi
print_success "Namespace deletion attempted"
else
print_status "Namespace bakery-ia not found"
fi
# Delete Kind cluster
print_status "Deleting Kind cluster..."
if kind get clusters | grep -q "bakery-ia-local"; then
kind delete cluster --name bakery-ia-local
print_success "Kind cluster deleted"
else
print_status "Kind cluster bakery-ia-local not found"
fi
# Stop local registry
print_status "Stopping local registry..."
if docker ps -a | grep -q "kind-registry"; then
docker stop kind-registry 2>/dev/null || true
docker rm kind-registry 2>/dev/null || true
print_success "Local registry removed"
else
print_status "Local registry not found"
fi
# Stop Colima
print_status "Stopping Colima..."
if colima list | grep -q "k8s-local"; then
colima stop --profile k8s-local
print_success "Colima stopped"
else
print_status "Colima profile k8s-local not found"
fi
print_success "Cleanup completed!"
echo "----------------------------------------"
}
# Function to check for required configuration files
check_config_files() {
print_status "Checking for required configuration files..."
# Check for kind-config.yaml
if [ ! -f kind-config.yaml ]; then
print_error "kind-config.yaml not found in current directory!"
print_error "Please ensure kind-config.yaml exists with your cluster configuration."
exit 1
fi
# Check for encryption directory if referenced in config
if grep -q "infrastructure/kubernetes/encryption" kind-config.yaml; then
if [ ! -d "./infrastructure/kubernetes/encryption" ]; then
print_warning "Encryption directory './infrastructure/kubernetes/encryption' not found"
print_warning "Some encryption configurations may not work properly"
fi
fi
print_success "Configuration files check completed"
}
# Function to create local registry
create_local_registry() {
local reg_name='kind-registry'
local reg_port='5001'
print_status "Setting up local Docker registry..."
# Create registry container unless it already exists
if [ "$(docker inspect -f '{{.State.Running}}' "${reg_name}" 2>/dev/null || true)" != 'true' ]; then
print_status "Creating registry container on port ${reg_port}..."
docker run \
-d --restart=always \
-p "127.0.0.1:${reg_port}:5000" \
--name "${reg_name}" \
registry:2
if [ $? -eq 0 ]; then
print_success "Local registry created at localhost:${reg_port}"
else
print_error "Failed to create local registry"
exit 1
fi
else
print_success "Local registry already running at localhost:${reg_port}"
fi
# Store registry info for later use
echo "${reg_name}:${reg_port}"
}
# Function to connect registry to Kind
connect_registry_to_kind() {
local reg_name='kind-registry'
local reg_port='5001'
print_status "Connecting registry to Kind network..."
# Connect the registry to the cluster network if not already connected
if [ "$(docker inspect -f='{{json .NetworkSettings.Networks.kind}}' "${reg_name}")" = 'null' ]; then
docker network connect "kind" "${reg_name}"
print_success "Registry connected to Kind network"
else
print_success "Registry already connected to Kind network"
fi
# Configure containerd in the Kind node to use the registry
print_status "Configuring containerd to use local registry..."
# Create the registry config directory
docker exec bakery-ia-local-control-plane mkdir -p /etc/containerd/certs.d/localhost:${reg_port}
# Add registry configuration
docker exec bakery-ia-local-control-plane sh -c "cat > /etc/containerd/certs.d/localhost:${reg_port}/hosts.toml <<EOF
server = \"http://localhost:${reg_port}\"
[host.\"http://${reg_name}:5000\"]
capabilities = [\"pull\", \"resolve\", \"push\"]
skip_verify = true
EOF"
# Restart containerd to pick up new configuration
docker exec bakery-ia-local-control-plane systemctl restart containerd
print_success "Containerd configured for local registry"
# Document the local registry
print_status "Documenting local registry in cluster..."
kubectl apply -f - <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
name: local-registry-hosting
namespace: kube-public
data:
localRegistryHosting.v1: |
host: "localhost:${reg_port}"
help: "https://kind.sigs.k8s.io/docs/user/local-registry/"
EOF
if [ $? -eq 0 ]; then
print_success "Registry documented in cluster"
else
print_warning "Failed to document registry (non-critical)"
fi
}
# Function to handle setup
setup() {
print_status "Starting setup process..."
# Check for required config files
check_config_files
# 1. Start Colima with adequate resources for SigNoz
print_status "Starting Colima with 8 CPU, 16GB memory, 120GB disk..."
colima start --cpu 8 --memory 16 --disk 120 --runtime docker --profile k8s-local
if [ $? -eq 0 ]; then
print_success "Colima started successfully"
# Increase inotify limits for Colima to prevent "too many open files" errors
print_status "Increasing inotify limits in Colima VM..."
colima ssh --profile k8s-local "sudo sysctl -w fs.inotify.max_user_watches=524288"
colima ssh --profile k8s-local "sudo sysctl -w fs.inotify.max_user_instances=512"
print_success "Inotify limits increased"
else
print_error "Failed to start Colima"
exit 1
fi
# 2. Create local registry before Kind cluster
create_local_registry
# 3. Create Kind cluster using existing configuration with registry support
print_status "Creating Kind cluster with registry configuration..."
if [ -f kind-config.yaml ]; then
print_status "Using kind-config.yaml with local registry support"
# Extract cluster name from config for verification
CLUSTER_NAME=$(grep -E "name:\s*" kind-config.yaml | head -1 | sed 's/name:\s*//' | tr -d '[:space:]' || echo "bakery-ia-local")
print_status "Creating cluster: $CLUSTER_NAME"
kind create cluster --config kind-config.yaml
if [ $? -eq 0 ]; then
print_success "Kind cluster created successfully"
else
print_error "Failed to create Kind cluster"
exit 1
fi
else
print_error "kind-config.yaml file not found!"
exit 1
fi
# 4. Connect registry to Kind network
connect_registry_to_kind
# 5. Install NGINX Ingress Controller
print_status "Installing NGINX Ingress Controller..."
# Apply the ingress-nginx manifest
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
if [ $? -eq 0 ]; then
print_success "NGINX Ingress Controller manifest applied"
else
print_error "Failed to apply NGINX Ingress Controller manifest"
exit 1
fi
# Wait for ingress-nginx pods to be ready with retry logic
wait_for_pods "ingress-nginx" "app.kubernetes.io/component=controller" 300
if [ $? -ne 0 ]; then
print_error "NGINX Ingress Controller failed to become ready"
print_status "Checking pod status for debugging..."
kubectl get pods -n ingress-nginx
kubectl describe pods -n ingress-nginx
exit 1
fi
print_success "NGINX Ingress Controller ready (using Kind's built-in NodePort configuration)"
# 6. Verify port mappings from kind-config.yaml
print_status "Verifying port mappings from configuration..."
# Extract ports from kind-config.yaml
HTTP_HOST_PORT=$(grep -A1 "containerPort: 30080" kind-config.yaml | grep "hostPort:" | awk '{print $2}' || echo "80")
HTTPS_HOST_PORT=$(grep -A1 "containerPort: 30443" kind-config.yaml | grep "hostPort:" | awk '{print $2}' || echo "443")
# Print cluster info
echo ""
print_success "Setup completed successfully!"
echo "----------------------------------------"
print_status "Cluster Information:"
echo " - Colima profile: k8s-local"
echo " - Kind cluster: $CLUSTER_NAME"
echo " - Local registry: localhost:5001"
echo ""
print_status "Port Mappings (configured in kind-config.yaml):"
echo " - HTTP Ingress: localhost:${HTTP_HOST_PORT} -> Kind NodePort 30080"
echo " - HTTPS Ingress: localhost:${HTTPS_HOST_PORT} -> Kind NodePort 30443"
echo " - Frontend Direct: localhost:3000 -> container:30300"
echo " - Gateway Direct: localhost:8000 -> container:30800"
echo ""
print_status "How to access your application:"
echo " 1. Start Tilt: tilt up"
echo " 2. Access via:"
echo " - Ingress: http://localhost (or https://localhost)"
echo " - Direct: http://localhost:3000 (frontend), http://localhost:8000 (gateway)"
echo " - Tilt UI: http://localhost:10350"
echo "----------------------------------------"
print_status "Local Registry Information:"
echo " - Registry URL: localhost:5001"
echo " - Images pushed to: localhost:5001/bakery/<service>"
echo " - Tiltfile already configured: default_registry('localhost:5001')"
echo "----------------------------------------"
}
# Function to show usage
usage() {
echo "Usage: $0 [option]"
echo ""
echo "Options:"
echo " cleanup Clean up all resources (namespace, cluster, colima)"
echo " setup Set up the complete environment"
echo " full Clean up first, then set up (default)"
echo " help Show this help message"
echo ""
echo "Requirements:"
echo " - kind-config.yaml must exist in current directory"
echo " - For encryption: ./infrastructure/kubernetes/encryption directory"
}
# Main script logic
case "${1:-full}" in
"cleanup")
cleanup
;;
"setup")
setup
;;
"full")
cleanup
setup
;;
"help"|"-h"|"--help")
usage
;;
*)
print_warning "Unknown option: $1"
echo ""
usage
exit 1
;;
esac

View File

@@ -0,0 +1,166 @@
#!/bin/bash
# Convenience script: Clean databases and regenerate all migrations in one command
# This wraps the two-step process into a single workflow
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration
NAMESPACE="${KUBE_NAMESPACE:-bakery-ia}"
SKIP_CONFIRMATION=false
APPLY_MIGRATIONS=false
VERBOSE=false
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
--namespace) NAMESPACE="$2"; shift 2 ;;
--yes) SKIP_CONFIRMATION=true; shift ;;
--apply) APPLY_MIGRATIONS=true; shift ;;
--verbose) VERBOSE=true; shift ;;
-h|--help)
echo "Usage: $0 [OPTIONS]"
echo ""
echo "This script performs the complete migration regeneration workflow:"
echo " 1. Clean all service databases"
echo " 2. Generate new migrations from models"
echo " 3. Optionally apply migrations"
echo ""
echo "Options:"
echo " --namespace NAME Use specific Kubernetes namespace (default: bakery-ia)"
echo " --yes Skip all confirmation prompts"
echo " --apply Apply migrations after generation"
echo " --verbose Enable detailed logging"
echo ""
echo "Examples:"
echo " $0 # Interactive mode (with confirmations)"
echo " $0 --yes --verbose # Automated mode with detailed output"
echo " $0 --apply # Generate and apply migrations"
exit 0
;;
*) echo "Unknown option: $1"; echo "Use --help for usage information"; exit 1 ;;
esac
done
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE}Complete Migration Regeneration Workflow${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
echo -e "${YELLOW}This script will:${NC}"
echo -e "${YELLOW} 1. Clean all service databases (DROP all tables)${NC}"
echo -e "${YELLOW} 2. Generate new migrations from models${NC}"
if [ "$APPLY_MIGRATIONS" = true ]; then
echo -e "${YELLOW} 3. Apply migrations to databases${NC}"
fi
echo ""
echo -e "${YELLOW}Namespace: $NAMESPACE${NC}"
echo ""
if [ "$SKIP_CONFIRMATION" = false ]; then
echo -e "${RED}⚠ WARNING: This will DROP ALL TABLES in all service databases!${NC}"
echo ""
read -p "Continue? (yes/no) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo -e "${YELLOW}Aborted.${NC}"
exit 0
fi
fi
echo ""
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE}Step 1: Cleaning Databases${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
# Build cleanup command
CLEANUP_CMD="./cleanup_databases_k8s.sh --namespace $NAMESPACE"
if [ "$SKIP_CONFIRMATION" = true ]; then
CLEANUP_CMD="$CLEANUP_CMD --yes"
fi
# Run cleanup
if ! $CLEANUP_CMD; then
echo -e "${RED}✗ Database cleanup failed!${NC}"
echo -e "${YELLOW}Cannot proceed with migration generation.${NC}"
exit 1
fi
echo ""
echo -e "${GREEN}✓ Database cleanup completed${NC}"
echo ""
# Wait a moment for database connections to settle
sleep 2
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE}Step 2: Generating Migrations${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
# Build migration command
MIGRATION_CMD="./regenerate_migrations_k8s.sh --namespace $NAMESPACE"
if [ "$VERBOSE" = true ]; then
MIGRATION_CMD="$MIGRATION_CMD --verbose"
fi
if [ "$APPLY_MIGRATIONS" = true ]; then
MIGRATION_CMD="$MIGRATION_CMD --apply"
fi
# Run migration generation (with automatic 'y' response)
if [ "$SKIP_CONFIRMATION" = true ]; then
echo "y" | $MIGRATION_CMD
else
$MIGRATION_CMD
fi
MIGRATION_EXIT_CODE=$?
echo ""
if [ $MIGRATION_EXIT_CODE -eq 0 ]; then
echo -e "${GREEN}========================================${NC}"
echo -e "${GREEN}✓ Workflow Completed Successfully!${NC}"
echo -e "${GREEN}========================================${NC}"
echo ""
echo -e "${YELLOW}Summary:${NC}"
echo -e " ${GREEN}${NC} Databases cleaned"
echo -e " ${GREEN}${NC} Migrations generated"
if [ "$APPLY_MIGRATIONS" = true ]; then
echo -e " ${GREEN}${NC} Migrations applied"
fi
echo ""
echo -e "${YELLOW}Generated migration files:${NC}"
find services/*/migrations/versions/ -name "*.py" -type f -mmin -5 2>/dev/null | while read file; do
size=$(wc -c < "$file" | tr -d ' ')
echo -e " ${GREEN}${NC} $file ($size bytes)"
done
echo ""
echo -e "${YELLOW}Next steps:${NC}"
echo -e " 1. Review the generated migrations above"
echo -e " 2. Verify migrations contain schema operations (not just 'pass')"
if [ "$APPLY_MIGRATIONS" = false ]; then
echo -e " 3. Apply migrations: ./regenerate_migrations_k8s.sh --apply"
echo -e " 4. Commit migrations: git add services/*/migrations/versions/*.py"
else
echo -e " 3. Commit migrations: git add services/*/migrations/versions/*.py"
fi
else
echo -e "${RED}========================================${NC}"
echo -e "${RED}✗ Migration Generation Failed${NC}"
echo -e "${RED}========================================${NC}"
echo ""
echo -e "${YELLOW}Check the log file for details.${NC}"
echo -e "${YELLOW}Common issues:${NC}"
echo -e " - Pod not running for some services"
echo -e " - Database connectivity issues"
echo -e " - Model import errors"
echo ""
exit 1
fi

View File

@@ -0,0 +1,796 @@
#!/bin/bash
# Script to regenerate Alembic migrations using Kubernetes local dev environment
# This script backs up existing migrations and generates new ones based on current models
set -euo pipefail # Exit on error, undefined variables, and pipe failures
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration
NAMESPACE="${KUBE_NAMESPACE:-bakery-ia}"
LOG_FILE="migration_script_$(date +%Y%m%d_%H%M%S).log"
BACKUP_RETENTION_DAYS=7
CONTAINER_SUFFIX="service" # Default container name suffix (e.g., pos-service)
# Parse command line arguments
DRY_RUN=false
SKIP_BACKUP=false
APPLY_MIGRATIONS=false
CHECK_EXISTING=false
VERBOSE=false
SKIP_DB_CHECK=false
while [[ $# -gt 0 ]]; do
case $1 in
--dry-run) DRY_RUN=true; shift ;;
--skip-backup) SKIP_BACKUP=true; shift ;;
--apply) APPLY_MIGRATIONS=true; shift ;;
--check-existing) CHECK_EXISTING=true; shift ;;
--verbose) VERBOSE=true; shift ;;
--skip-db-check) SKIP_DB_CHECK=true; shift ;;
--namespace) NAMESPACE="$2"; shift 2 ;;
-h|--help)
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Options:"
echo " --dry-run Show what would be done without making changes"
echo " --skip-backup Skip backing up existing migrations"
echo " --apply Automatically apply migrations after generation"
echo " --check-existing Check for and copy existing migrations from pods first"
echo " --verbose Enable detailed logging"
echo " --skip-db-check Skip database connectivity check"
echo " --namespace NAME Use specific Kubernetes namespace (default: bakery-ia)"
echo ""
echo "Examples:"
echo " $0 --namespace dev --dry-run # Simulate migration regeneration"
echo " $0 --apply --verbose # Generate and apply migrations with detailed logs"
echo " $0 --skip-db-check # Skip database connectivity check"
exit 0
;;
*) echo "Unknown option: $1"; echo "Use --help for usage information"; exit 1 ;;
esac
done
# List of all services
SERVICES=(
"pos" "sales" "recipes" "training" "auth" "orders" "inventory"
"suppliers" "tenant" "notification" "alert-processor" "forecasting"
"external" "production" "demo-session"
)
# Backup directory
BACKUP_DIR="migrations_backup_$(date +%Y%m%d_%H%M%S)"
mkdir -p "$BACKUP_DIR"
# Initialize log file
touch "$LOG_FILE"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Starting migration regeneration" >> "$LOG_FILE"
# Function to perform pre-flight checks
preflight_checks() {
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE}Pre-flight Checks${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
local checks_passed=true
# Check kubectl
echo -e "${YELLOW}Checking kubectl...${NC}"
if ! command -v kubectl &> /dev/null; then
echo -e "${RED}✗ kubectl not found${NC}"
log_message "ERROR" "kubectl not found"
checks_passed=false
else
KUBECTL_VERSION=$(kubectl version --client --short 2>/dev/null | grep -oP 'v\d+\.\d+\.\d+' || echo "unknown")
echo -e "${GREEN}✓ kubectl found (version: $KUBECTL_VERSION)${NC}"
fi
# Check cluster connectivity
echo -e "${YELLOW}Checking Kubernetes cluster connectivity...${NC}"
if ! kubectl cluster-info &> /dev/null; then
echo -e "${RED}✗ Cannot connect to Kubernetes cluster${NC}"
log_message "ERROR" "Cannot connect to Kubernetes cluster"
checks_passed=false
else
CLUSTER_NAME=$(kubectl config current-context 2>/dev/null || echo "unknown")
echo -e "${GREEN}✓ Connected to cluster: $CLUSTER_NAME${NC}"
fi
# Check namespace exists
echo -e "${YELLOW}Checking namespace '$NAMESPACE'...${NC}"
if ! kubectl get namespace "$NAMESPACE" &> /dev/null; then
echo -e "${RED}✗ Namespace '$NAMESPACE' not found${NC}"
log_message "ERROR" "Namespace '$NAMESPACE' not found"
checks_passed=false
else
echo -e "${GREEN}✓ Namespace exists${NC}"
fi
# Check if all service pods are running
echo -e "${YELLOW}Checking service pods...${NC}"
local pods_found=0
local pods_running=0
for service in "${SERVICES[@]}"; do
local pod_name=$(kubectl get pods -n "$NAMESPACE" -l "app.kubernetes.io/name=${service}-service" --field-selector=status.phase=Running -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || echo "")
if [ -n "$pod_name" ]; then
pods_found=$((pods_found + 1))
pods_running=$((pods_running + 1))
fi
done
echo -e "${GREEN}✓ Found $pods_running/${#SERVICES[@]} service pods running${NC}"
if [ $pods_running -lt ${#SERVICES[@]} ]; then
echo -e "${YELLOW}⚠ Not all service pods are running${NC}"
echo -e "${YELLOW} Missing services will be skipped${NC}"
fi
# Check database connectivity for running services
echo -e "${YELLOW}Checking database connectivity (sample)...${NC}"
local sample_service="auth"
local sample_pod=$(kubectl get pods -n "$NAMESPACE" -l "app.kubernetes.io/name=${sample_service}-service" --field-selector=status.phase=Running -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || echo "")
if [ -n "$sample_pod" ]; then
local db_check=$(kubectl exec -n "$NAMESPACE" "$sample_pod" -c "${sample_service}-service" -- sh -c "python3 -c 'import asyncpg; print(\"OK\")' 2>/dev/null" || echo "FAIL")
if [ "$db_check" = "OK" ]; then
echo -e "${GREEN}✓ Database drivers available (asyncpg)${NC}"
else
echo -e "${YELLOW}⚠ Database driver check failed${NC}"
fi
else
echo -e "${YELLOW}⚠ Cannot check database connectivity (no sample pod running)${NC}"
fi
# Check local directory structure
echo -e "${YELLOW}Checking local directory structure...${NC}"
local dirs_found=0
for service in "${SERVICES[@]}"; do
local service_dir=$(echo "$service" | tr '-' '_')
if [ -d "services/$service_dir/migrations" ]; then
dirs_found=$((dirs_found + 1))
fi
done
echo -e "${GREEN}✓ Found $dirs_found/${#SERVICES[@]} service migration directories${NC}"
# Check disk space
echo -e "${YELLOW}Checking disk space...${NC}"
local available_space=$(df -h . | tail -1 | awk '{print $4}')
echo -e "${GREEN}✓ Available disk space: $available_space${NC}"
echo ""
if [ "$checks_passed" = false ]; then
echo -e "${RED}========================================${NC}"
echo -e "${RED}Pre-flight checks failed!${NC}"
echo -e "${RED}========================================${NC}"
echo ""
read -p "Continue anyway? (y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo -e "${RED}Aborted.${NC}"
exit 1
fi
else
echo -e "${GREEN}========================================${NC}"
echo -e "${GREEN}All pre-flight checks passed!${NC}"
echo -e "${GREEN}========================================${NC}"
fi
echo ""
}
# Run pre-flight checks
preflight_checks
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE}Migration Regeneration Script (K8s)${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
if [ "$DRY_RUN" = true ]; then
echo -e "${YELLOW}🔍 DRY RUN MODE - No changes will be made${NC}"
echo ""
fi
echo -e "${YELLOW}This script will:${NC}"
if [ "$CHECK_EXISTING" = true ]; then
echo -e "${YELLOW}1. Check for existing migrations in pods and copy them${NC}"
fi
if [ "$SKIP_BACKUP" = false ]; then
echo -e "${YELLOW}2. Backup existing migration files${NC}"
fi
echo -e "${YELLOW}3. Generate new migrations in Kubernetes pods${NC}"
echo -e "${YELLOW}4. Copy generated files back to local machine${NC}"
if [ "$APPLY_MIGRATIONS" = true ]; then
echo -e "${YELLOW}5. Apply migrations to databases${NC}"
fi
if [ "$SKIP_BACKUP" = false ]; then
echo -e "${YELLOW}6. Keep the backup in: $BACKUP_DIR${NC}"
fi
echo ""
echo -e "${YELLOW}Using Kubernetes namespace: $NAMESPACE${NC}"
echo -e "${YELLOW}Logs will be saved to: $LOG_FILE${NC}"
echo ""
if [ "$DRY_RUN" = false ]; then
read -p "Continue? (y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo -e "${RED}Aborted.${NC}"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Aborted by user" >> "$LOG_FILE"
exit 1
fi
fi
# Kubernetes setup already verified in pre-flight checks
# Function to get a running pod for a service
get_running_pod() {
local service=$1
local pod_name=""
local selectors=(
"app.kubernetes.io/name=${service}-service,app.kubernetes.io/component=microservice"
"app.kubernetes.io/name=${service}-service,app.kubernetes.io/component=worker"
"app.kubernetes.io/name=${service}-service"
"app=${service}-service,component=${service}" # Fallback for demo-session
"app=${service}-service" # Additional fallback
)
for selector in "${selectors[@]}"; do
pod_name=$(kubectl get pods -n "$NAMESPACE" -l "$selector" --field-selector=status.phase=Running -o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
if [ -n "$pod_name" ]; then
echo "$pod_name"
return 0
fi
done
echo ""
return 1
}
# Function to log messages
log_message() {
local level=$1
local message=$2
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $level: $message" >> "$LOG_FILE"
if [ "$VERBOSE" = true ] || [ "$level" = "ERROR" ]; then
echo -e "${YELLOW}$message${NC}"
fi
}
# Check for existing migrations in pods if requested
if [ "$CHECK_EXISTING" = true ]; then
echo -e "${BLUE}Step 1.5: Checking for existing migrations in pods...${NC}"
echo ""
FOUND_COUNT=0
COPIED_COUNT=0
for service in "${SERVICES[@]}"; do
service_dir=$(echo "$service" | tr '-' '_')
echo -e "${YELLOW}Checking $service...${NC}"
# Find a running pod
POD_NAME=$(get_running_pod "$service")
if [ -z "$POD_NAME" ]; then
echo -e "${YELLOW}⚠ Pod not found, skipping${NC}"
log_message "WARNING" "No running pod found for $service"
continue
fi
# Check container availability
CONTAINER="${service}-${CONTAINER_SUFFIX}"
if ! kubectl get pod -n "$NAMESPACE" "$POD_NAME" -o jsonpath='{.spec.containers[*].name}' | grep -qw "$CONTAINER"; then
echo -e "${RED}✗ Container $CONTAINER not found in pod $POD_NAME, skipping${NC}"
log_message "ERROR" "Container $CONTAINER not found in pod $POD_NAME for $service"
continue
fi
# Check if migration files exist in the pod
EXISTING_FILES=$(kubectl exec -n "$NAMESPACE" "$POD_NAME" -c "$CONTAINER" -- sh -c "ls /app/migrations/versions/*.py 2>/dev/null | grep -v __pycache__ | grep -v __init__.py" 2>/dev/null || echo "")
if [ -n "$EXISTING_FILES" ]; then
FILE_COUNT=$(echo "$EXISTING_FILES" | wc -l | tr -d ' ')
echo -e "${GREEN}✓ Found $FILE_COUNT migration file(s) in pod${NC}"
FOUND_COUNT=$((FOUND_COUNT + 1))
# Create local versions directory
mkdir -p "services/$service_dir/migrations/versions"
# Copy each file
for pod_file in $EXISTING_FILES; do
filename=$(basename "$pod_file")
if [ "$DRY_RUN" = true ]; then
echo -e "${BLUE}[DRY RUN] Would copy: $filename${NC}"
log_message "INFO" "[DRY RUN] Would copy $filename for $service"
else
if kubectl cp -n "$NAMESPACE" "$POD_NAME:$pod_file" "services/$service_dir/migrations/versions/$filename" -c "$CONTAINER" 2>>"$LOG_FILE"; then
echo -e "${GREEN}✓ Copied: $filename${NC}"
COPIED_COUNT=$((COPIED_COUNT + 1))
log_message "INFO" "Copied $filename for $service"
# Display brief summary
echo -e "${BLUE}Preview:${NC}"
grep "def upgrade" "services/$service_dir/migrations/versions/$filename" | head -1
grep "op\." "services/$service_dir/migrations/versions/$filename" | head -3 | sed 's/^/ /'
else
echo -e "${RED}✗ Failed to copy: $filename${NC}"
log_message "ERROR" "Failed to copy $filename for $service"
fi
fi
done
else
echo -e "${YELLOW}⚠ No migration files found in pod${NC}"
log_message "WARNING" "No migration files found in pod for $service"
fi
echo ""
done
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE}Existing Migrations Check Summary${NC}"
echo -e "${BLUE}========================================${NC}"
echo -e "${GREEN}Services with migrations: $FOUND_COUNT${NC}"
if [ "$DRY_RUN" = false ]; then
echo -e "${GREEN}Files copied: $COPIED_COUNT${NC}"
fi
echo ""
if [ "$FOUND_COUNT" = 0 ] && [ "$DRY_RUN" = false ]; then
read -p "Do you want to continue with regeneration? (y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo -e "${YELLOW}Stopping. Existing migrations have been copied.${NC}"
log_message "INFO" "Stopped after copying existing migrations"
exit 0
fi
fi
fi
# Backup existing migrations
if [ "$SKIP_BACKUP" = false ] && [ "$DRY_RUN" = false ]; then
echo -e "${BLUE}Step 2: Backing up existing migrations...${NC}"
BACKUP_COUNT=0
for service in "${SERVICES[@]}"; do
service_dir=$(echo "$service" | tr '-' '_')
if [ -d "services/$service_dir/migrations/versions" ] && [ -n "$(ls services/$service_dir/migrations/versions/*.py 2>/dev/null)" ]; then
echo -e "${YELLOW}Backing up $service migrations...${NC}"
mkdir -p "$BACKUP_DIR/$service_dir/versions"
cp -r "services/$service_dir/migrations/versions/"*.py "$BACKUP_DIR/$service_dir/versions/" 2>>"$LOG_FILE"
BACKUP_COUNT=$((BACKUP_COUNT + 1))
log_message "INFO" "Backed up migrations for $service to $BACKUP_DIR/$service_dir/versions"
else
echo -e "${YELLOW}No migration files to backup for $service${NC}"
fi
done
if [ "$BACKUP_COUNT" -gt 0 ]; then
echo -e "${GREEN}✓ Backup complete: $BACKUP_DIR ($BACKUP_COUNT services)${NC}"
else
echo -e "${YELLOW}No migrations backed up (no migration files found)${NC}"
fi
echo ""
elif [ "$SKIP_BACKUP" = true ]; then
echo -e "${YELLOW}Skipping backup step (--skip-backup flag)${NC}"
log_message "INFO" "Backup skipped due to --skip-backup flag"
echo ""
fi
# Clean up old backups
find . -maxdepth 1 -type d -name 'migrations_backup_*' -mtime +"$BACKUP_RETENTION_DAYS" -exec rm -rf {} \; 2>/dev/null || true
log_message "INFO" "Cleaned up backups older than $BACKUP_RETENTION_DAYS days"
echo -e "${BLUE}Step 3: Generating new migrations in Kubernetes...${NC}"
echo ""
SUCCESS_COUNT=0
FAILED_COUNT=0
FAILED_SERVICES=()
# Function to process a single service
process_service() {
local service=$1
local service_dir=$(echo "$service" | tr '-' '_')
local db_env_var=$(echo "$service" | tr '[:lower:]-' '[:upper:]_')_DATABASE_URL # e.g., pos -> POS_DATABASE_URL, alert-processor -> ALERT_PROCESSOR_DATABASE_URL
echo -e "${BLUE}----------------------------------------${NC}"
echo -e "${BLUE}Processing: $service${NC}"
echo -e "${BLUE}----------------------------------------${NC}"
log_message "INFO" "Starting migration generation for $service"
# Skip if no local migrations directory and --check-existing is not set
if [ ! -d "services/$service_dir/migrations/versions" ] && [ "$CHECK_EXISTING" = false ]; then
echo -e "${YELLOW}⚠ No local migrations/versions directory for $service, skipping...${NC}"
log_message "WARNING" "No local migrations/versions directory for $service"
return
fi
# Find a running pod
echo -e "${YELLOW}Finding $service pod in namespace $NAMESPACE...${NC}"
POD_NAME=$(get_running_pod "$service")
if [ -z "$POD_NAME" ]; then
echo -e "${RED}✗ No running pod found for $service. Skipping...${NC}"
log_message "ERROR" "No running pod found for $service"
FAILED_COUNT=$((FAILED_COUNT + 1))
FAILED_SERVICES+=("$service (pod not found)")
return
fi
echo -e "${GREEN}✓ Found pod: $POD_NAME${NC}"
log_message "INFO" "Found pod $POD_NAME for $service"
# Check container availability
CONTAINER="${service}-${CONTAINER_SUFFIX}"
if ! kubectl get pod -n "$NAMESPACE" "$POD_NAME" -o jsonpath='{.spec.containers[*].name}' | grep -qw "$CONTAINER"; then
echo -e "${RED}✗ Container $CONTAINER not found in pod $POD_NAME, skipping${NC}"
log_message "ERROR" "Container $CONTAINER not found in pod $POD_NAME for $service"
FAILED_COUNT=$((FAILED_COUNT + 1))
FAILED_SERVICES+=("$service (container not found)")
return
fi
# Verify database connectivity
if [ "$SKIP_DB_CHECK" = false ]; then
echo -e "${YELLOW}Verifying database connectivity using $db_env_var...${NC}"
# Check if asyncpg is installed
ASYNCPG_CHECK=$(kubectl exec -n "$NAMESPACE" "$POD_NAME" -c "$CONTAINER" -- sh -c "python3 -c \"import asyncpg; print('asyncpg OK')\" 2>/dev/null" || echo "asyncpg MISSING")
if [[ "$ASYNCPG_CHECK" != "asyncpg OK" ]]; then
echo -e "${YELLOW}Installing asyncpg...${NC}"
kubectl exec -n "$NAMESPACE" "$POD_NAME" -c "$CONTAINER" -- sh -c "python3 -m pip install --quiet asyncpg" 2>>"$LOG_FILE"
fi
# Check for database URL
DB_URL_CHECK=$(kubectl exec -n "$NAMESPACE" "$POD_NAME" -c "$CONTAINER" -- sh -c "env | grep $db_env_var" 2>/dev/null || echo "")
if [ -z "$DB_URL_CHECK" ]; then
echo -e "${RED}✗ Environment variable $db_env_var not found in pod $POD_NAME${NC}"
echo -e "${YELLOW}Available environment variables:${NC}"
kubectl exec -n "$NAMESPACE" "$POD_NAME" -c "$CONTAINER" -- sh -c "env" 2>>"$LOG_FILE" | grep -i "database" || echo "No database-related variables found"
log_message "ERROR" "Environment variable $db_env_var not found for $service in pod $POD_NAME"
FAILED_COUNT=$((FAILED_COUNT + 1))
FAILED_SERVICES+=("$service (missing $db_env_var)")
return
fi
# Log redacted database URL for debugging
DB_URL=$(kubectl exec -n "$NAMESPACE" "$POD_NAME" -c "$CONTAINER" -- sh -c "echo \$"$db_env_var"" 2>/dev/null | sed 's/\(password=\)[^@]*/\1[REDACTED]/')
log_message "INFO" "Using database URL for $service: $DB_URL"
# Perform async database connectivity check
DB_CHECK_OUTPUT=$(kubectl exec -n "$NAMESPACE" "$POD_NAME" -c "$CONTAINER" -- sh -c "cd /app && PYTHONPATH=/app:/app/shared:\$PYTHONPATH python3 -c \"import asyncio; from sqlalchemy.ext.asyncio import create_async_engine; async def check_db(): engine = create_async_engine(os.getenv('$db_env_var')); async with engine.connect() as conn: pass; await engine.dispose(); print('DB OK'); asyncio.run(check_db())\" 2>&1" || echo "DB ERROR")
if [[ "$DB_CHECK_OUTPUT" == *"DB OK"* ]]; then
echo -e "${GREEN}✓ Database connection verified${NC}"
log_message "INFO" "Database connection verified for $service"
else
echo -e "${RED}✗ Database connection failed for $service${NC}"
echo -e "${YELLOW}Error details: $DB_CHECK_OUTPUT${NC}"
log_message "ERROR" "Database connection failed for $service: $DB_CHECK_OUTPUT"
FAILED_COUNT=$((FAILED_COUNT + 1))
FAILED_SERVICES+=("$service (database connection failed)")
return
fi
else
echo -e "${YELLOW}Skipping database connectivity check (--skip-db-check)${NC}"
log_message "INFO" "Skipped database connectivity check for $service"
fi
# Reset alembic version tracking
echo -e "${YELLOW}Resetting alembic version tracking...${NC}"
kubectl exec -n "$NAMESPACE" "$POD_NAME" -c "$CONTAINER" -- sh -c "cd /app && PYTHONPATH=/app:/app/shared:\$PYTHONPATH alembic downgrade base" 2>&1 | tee -a "$LOG_FILE" | grep -v "^INFO" || true
log_message "INFO" "Attempted alembic downgrade for $service"
# Option 1: Complete database schema reset using CASCADE
echo -e "${YELLOW}Performing complete database schema reset...${NC}"
SCHEMA_DROP_RESULT=$(kubectl exec -n "$NAMESPACE" "$POD_NAME" -c "$CONTAINER" -- sh -c "cd /app && PYTHONPATH=/app:/app/shared:\$PYTHONPATH python3 << 'EOFPYTHON'
import asyncio
import os
from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy import text
async def reset_database():
try:
engine = create_async_engine(os.getenv('$db_env_var'))
async with engine.begin() as conn:
# Drop and recreate public schema - cleanest approach
await conn.execute(text('DROP SCHEMA IF EXISTS public CASCADE'))
await conn.execute(text('CREATE SCHEMA public'))
await conn.execute(text('GRANT ALL ON SCHEMA public TO PUBLIC'))
await engine.dispose()
print('SUCCESS: Database schema reset complete')
return 0
except Exception as e:
print(f'ERROR: {str(e)}')
return 1
exit(asyncio.run(reset_database()))
EOFPYTHON
" 2>&1)
echo "$SCHEMA_DROP_RESULT" >> "$LOG_FILE"
if echo "$SCHEMA_DROP_RESULT" | grep -q "SUCCESS"; then
echo -e "${GREEN}✓ Database schema reset successfully${NC}"
log_message "INFO" "Database schema reset for $service"
else
echo -e "${RED}✗ Database schema reset failed${NC}"
echo -e "${YELLOW}Error details:${NC}"
echo "$SCHEMA_DROP_RESULT"
log_message "ERROR" "Database schema reset failed for $service: $SCHEMA_DROP_RESULT"
# Try alternative approach: Drop individual tables from database (not just models)
echo -e "${YELLOW}Attempting alternative: dropping all existing tables individually...${NC}"
TABLE_DROP_RESULT=$(kubectl exec -n "$NAMESPACE" "$POD_NAME" -c "$CONTAINER" -- sh -c "cd /app && PYTHONPATH=/app:/app/shared:\$PYTHONPATH python3 << 'EOFPYTHON'
import asyncio
import os
from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy import text
async def drop_all_tables():
try:
engine = create_async_engine(os.getenv('$db_env_var'))
async with engine.begin() as conn:
# Get all tables from database
result = await conn.execute(text(\"\"\"
SELECT tablename
FROM pg_tables
WHERE schemaname = 'public'
\"\"\"))
tables = [row[0] for row in result]
# Drop each table
for table in tables:
await conn.execute(text(f'DROP TABLE IF EXISTS \"{table}\" CASCADE'))
print(f'SUCCESS: Dropped {len(tables)} tables: {tables}')
await engine.dispose()
return 0
except Exception as e:
print(f'ERROR: {str(e)}')
return 1
exit(asyncio.run(drop_all_tables()))
EOFPYTHON
" 2>&1)
echo "$TABLE_DROP_RESULT" >> "$LOG_FILE"
if echo "$TABLE_DROP_RESULT" | grep -q "SUCCESS"; then
echo -e "${GREEN}✓ All tables dropped successfully${NC}"
log_message "INFO" "All tables dropped for $service"
else
echo -e "${RED}✗ Failed to drop tables${NC}"
echo -e "${YELLOW}Error details:${NC}"
echo "$TABLE_DROP_RESULT"
log_message "ERROR" "Failed to drop tables for $service: $TABLE_DROP_RESULT"
FAILED_COUNT=$((FAILED_COUNT + 1))
FAILED_SERVICES+=("$service (database cleanup failed)")
return
fi
fi
# Verify database is empty
echo -e "${YELLOW}Verifying database is clean...${NC}"
VERIFY_RESULT=$(kubectl exec -n "$NAMESPACE" "$POD_NAME" -c "$CONTAINER" -- sh -c "cd /app && PYTHONPATH=/app:/app/shared:\$PYTHONPATH python3 << 'EOFPYTHON'
import asyncio
import os
from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy import text
async def verify_empty():
engine = create_async_engine(os.getenv('$db_env_var'))
async with engine.connect() as conn:
result = await conn.execute(text(\"\"\"
SELECT COUNT(*)
FROM pg_tables
WHERE schemaname = 'public'
\"\"\"))
count = result.scalar()
print(f'Tables remaining: {count}')
await engine.dispose()
return count
exit(asyncio.run(verify_empty()))
EOFPYTHON
" 2>&1)
echo "$VERIFY_RESULT" >> "$LOG_FILE"
echo -e "${BLUE}$VERIFY_RESULT${NC}"
# Initialize alembic version table after schema reset
echo -e "${YELLOW}Initializing alembic version tracking...${NC}"
ALEMBIC_INIT_OUTPUT=$(kubectl exec -n "$NAMESPACE" "$POD_NAME" -c "$CONTAINER" -- sh -c "cd /app && PYTHONPATH=/app:/app/shared:\$PYTHONPATH alembic stamp base" 2>&1)
ALEMBIC_INIT_EXIT_CODE=$?
echo "$ALEMBIC_INIT_OUTPUT" >> "$LOG_FILE"
if [ $ALEMBIC_INIT_EXIT_CODE -eq 0 ]; then
echo -e "${GREEN}✓ Alembic version tracking initialized${NC}"
log_message "INFO" "Alembic version tracking initialized for $service"
else
echo -e "${YELLOW}⚠ Alembic initialization warning (may be normal)${NC}"
log_message "WARNING" "Alembic initialization for $service: $ALEMBIC_INIT_OUTPUT"
fi
# Remove old migration files in pod
echo -e "${YELLOW}Removing old migration files in pod...${NC}"
kubectl exec -n "$NAMESPACE" "$POD_NAME" -c "$CONTAINER" -- sh -c "rm -rf /app/migrations/versions/*.py /app/migrations/versions/__pycache__" 2>>"$LOG_FILE" || log_message "WARNING" "Failed to remove old migration files for $service"
# Ensure dependencies
echo -e "${YELLOW}Ensuring python-dateutil and asyncpg are installed...${NC}"
kubectl exec -n "$NAMESPACE" "$POD_NAME" -c "$CONTAINER" -- sh -c "python3 -m pip install --quiet python-dateutil asyncpg" 2>>"$LOG_FILE"
# Generate migration
echo -e "${YELLOW}Running alembic autogenerate in pod...${NC}"
MIGRATION_TIMESTAMP=$(date +%Y%m%d_%H%M)
MIGRATION_OUTPUT=$(kubectl exec -n "$NAMESPACE" "$POD_NAME" -c "$CONTAINER" -- sh -c "cd /app && PYTHONPATH=/app:/app/shared:\$PYTHONPATH python3 -m alembic revision --autogenerate -m \"initial_schema_$MIGRATION_TIMESTAMP\"" 2>&1)
MIGRATION_EXIT_CODE=$?
echo "$MIGRATION_OUTPUT" >> "$LOG_FILE"
if [ $MIGRATION_EXIT_CODE -eq 0 ]; then
echo -e "${GREEN}✓ Migration generated in pod${NC}"
log_message "INFO" "Migration generated for $service"
# Copy migration file
MIGRATION_FILE=$(kubectl exec -n "$NAMESPACE" "$POD_NAME" -c "$CONTAINER" -- sh -c "ls -t /app/migrations/versions/*.py 2>/dev/null | head -1" || echo "")
if [ -z "$MIGRATION_FILE" ]; then
echo -e "${RED}✗ No migration file found in pod${NC}"
log_message "ERROR" "No migration file generated for $service"
FAILED_COUNT=$((FAILED_COUNT + 1))
FAILED_SERVICES+=("$service (no file generated)")
return
fi
MIGRATION_FILENAME=$(basename "$MIGRATION_FILE")
mkdir -p "services/$service_dir/migrations/versions"
# Copy file with better error handling
echo -e "${YELLOW}Copying migration file from pod...${NC}"
CP_OUTPUT=$(kubectl cp -n "$NAMESPACE" "$POD_NAME:$MIGRATION_FILE" "services/$service_dir/migrations/versions/$MIGRATION_FILENAME" -c "$CONTAINER" 2>&1)
CP_EXIT_CODE=$?
echo "$CP_OUTPUT" >> "$LOG_FILE"
# Verify the file was actually copied
if [ $CP_EXIT_CODE -eq 0 ] && [ -f "services/$service_dir/migrations/versions/$MIGRATION_FILENAME" ]; then
LOCAL_FILE_SIZE=$(wc -c < "services/$service_dir/migrations/versions/$MIGRATION_FILENAME" | tr -d ' ')
if [ "$LOCAL_FILE_SIZE" -gt 0 ]; then
echo -e "${GREEN}✓ Migration file copied: $MIGRATION_FILENAME ($LOCAL_FILE_SIZE bytes)${NC}"
log_message "INFO" "Copied $MIGRATION_FILENAME for $service ($LOCAL_FILE_SIZE bytes)"
SUCCESS_COUNT=$((SUCCESS_COUNT + 1))
# Validate migration content
echo -e "${YELLOW}Validating migration content...${NC}"
if grep -E "op\.(create_table|add_column|create_index|alter_column|drop_table|drop_column|create_foreign_key)" "services/$service_dir/migrations/versions/$MIGRATION_FILENAME" >/dev/null; then
echo -e "${GREEN}✓ Migration contains schema operations${NC}"
log_message "INFO" "Migration contains schema operations for $service"
elif grep -q "pass" "services/$service_dir/migrations/versions/$MIGRATION_FILENAME" && grep -q "def upgrade()" "services/$service_dir/migrations/versions/$MIGRATION_FILENAME"; then
echo -e "${YELLOW}⚠ WARNING: Migration is empty (no schema changes detected)${NC}"
echo -e "${YELLOW}⚠ This usually means tables already exist in database matching the models${NC}"
log_message "WARNING" "Empty migration generated for $service - possible database cleanup issue"
else
echo -e "${GREEN}✓ Migration file created${NC}"
fi
# Display summary
echo -e "${BLUE}Migration summary:${NC}"
grep -E "^def (upgrade|downgrade)" "services/$service_dir/migrations/versions/$MIGRATION_FILENAME" | head -2
echo -e "${BLUE}Operations:${NC}"
grep "op\." "services/$service_dir/migrations/versions/$MIGRATION_FILENAME" | head -5 || echo " (none found)"
else
echo -e "${RED}✗ Migration file is empty (0 bytes)${NC}"
log_message "ERROR" "Migration file is empty for $service"
rm -f "services/$service_dir/migrations/versions/$MIGRATION_FILENAME"
FAILED_COUNT=$((FAILED_COUNT + 1))
FAILED_SERVICES+=("$service (empty file)")
fi
else
echo -e "${RED}✗ Failed to copy migration file${NC}"
echo -e "${YELLOW}kubectl cp exit code: $CP_EXIT_CODE${NC}"
echo -e "${YELLOW}kubectl cp output: $CP_OUTPUT${NC}"
log_message "ERROR" "Failed to copy migration file for $service: $CP_OUTPUT"
FAILED_COUNT=$((FAILED_COUNT + 1))
FAILED_SERVICES+=("$service (copy failed)")
fi
else
echo -e "${RED}✗ Failed to generate migration${NC}"
log_message "ERROR" "Failed to generate migration for $service"
FAILED_COUNT=$((FAILED_COUNT + 1))
FAILED_SERVICES+=("$service (generation failed)")
fi
}
# Process services sequentially
for service in "${SERVICES[@]}"; do
process_service "$service"
done
# Summary
echo ""
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE}Summary${NC}"
echo -e "${BLUE}========================================${NC}"
echo -e "${GREEN}✓ Successful: $SUCCESS_COUNT services${NC}"
echo -e "${RED}✗ Failed: $FAILED_COUNT services${NC}"
if [ "$FAILED_COUNT" -gt 0 ]; then
echo ""
echo -e "${RED}Failed services:${NC}"
for failed_service in "${FAILED_SERVICES[@]}"; do
echo -e "${RED} - $failed_service${NC}"
done
fi
echo ""
echo -e "${YELLOW}Backup location: $BACKUP_DIR${NC}"
echo -e "${YELLOW}Log file: $LOG_FILE${NC}"
echo ""
# Apply migrations if requested
if [ "$APPLY_MIGRATIONS" = true ] && [ "$DRY_RUN" = false ] && [ "$SUCCESS_COUNT" -gt 0 ]; then
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE}Applying Migrations${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
APPLIED_COUNT=0
APPLY_FAILED_COUNT=0
for service in "${SERVICES[@]}"; do
service_dir=$(echo "$service" | tr '-' '_')
local db_env_var=$(echo "$service" | tr '[:lower:]-' '[:upper:]_')_DATABASE_URL
if [ ! -d "services/$service_dir/migrations/versions" ] || [ -z "$(ls services/$service_dir/migrations/versions/*.py 2>/dev/null)" ]; then
continue
fi
echo -e "${BLUE}Applying migrations for: $service${NC}"
POD_NAME=$(get_running_pod "$service")
if [ -z "$POD_NAME" ]; then
echo -e "${YELLOW}⚠ Pod not found for $service, skipping...${NC}"
log_message "WARNING" "No running pod found for $service during migration application"
continue
fi
CONTAINER="${service}-${CONTAINER_SUFFIX}"
if ! kubectl get pod -n "$NAMESPACE" "$POD_NAME" -o jsonpath='{.spec.containers[*].name}' | grep -qw "$CONTAINER"; then
echo -e "${RED}✗ Container $CONTAINER not found in pod $POD_NAME, skipping${NC}"
log_message "ERROR" "Container $CONTAINER not found in pod $POD_NAME for $service"
continue
fi
if kubectl exec -n "$NAMESPACE" "$POD_NAME" -c "$CONTAINER" -- sh -c "cd /app && PYTHONPATH=/app:/app/shared:\$PYTHONPATH alembic upgrade head" 2>>"$LOG_FILE"; then
echo -e "${GREEN}✓ Migrations applied successfully for $service${NC}"
log_message "INFO" "Migrations applied for $service"
APPLIED_COUNT=$((APPLIED_COUNT + 1))
else
echo -e "${RED}✗ Failed to apply migrations for $service${NC}"
log_message "ERROR" "Failed to apply migrations for $service"
APPLY_FAILED_COUNT=$((APPLY_FAILED_COUNT + 1))
fi
echo ""
done
echo -e "${BLUE}Migration Application Summary:${NC}"
echo -e "${GREEN}✓ Applied: $APPLIED_COUNT services${NC}"
echo -e "${RED}✗ Failed: $APPLY_FAILED_COUNT services${NC}"
echo ""
fi
# Clean up temporary files
rm -f /tmp/*_migration.log /tmp/*_downgrade.log /tmp/*_apply.log 2>/dev/null || true
log_message "INFO" "Cleaned up temporary files"
echo -e "${BLUE}Next steps:${NC}"
echo -e "${YELLOW}1. Review the generated migrations in services/*/migrations/versions/${NC}"
echo -e "${YELLOW}2. Compare with the backup in $BACKUP_DIR${NC}"
echo -e "${YELLOW}3. Check logs in $LOG_FILE for details${NC}"
echo -e "${YELLOW}4. Test migrations by applying them:${NC}"
echo -e " ${GREEN}kubectl exec -n $NAMESPACE -it <pod-name> -c <service>-${CONTAINER_SUFFIX} -- alembic upgrade head${NC}"
echo -e "${YELLOW}5. Verify tables were created:${NC}"
echo -e " ${GREEN}kubectl exec -n $NAMESPACE -it <pod-name> -c <service>-${CONTAINER_SUFFIX} -- python3 -c \"${NC}"
echo -e " ${GREEN}import asyncio; from sqlalchemy.ext.asyncio import create_async_engine; from sqlalchemy import inspect; async def check_tables(): engine = create_async_engine(os.getenv('<SERVICE>_DATABASE_URL')); async with engine.connect() as conn: print(inspect(conn).get_table_names()); await engine.dispose(); asyncio.run(check_tables())${NC}"
echo -e " ${GREEN}\"${NC}"
echo -e "${YELLOW}6. If issues occur, restore from backup:${NC}"
echo -e " ${GREEN}cp -r $BACKUP_DIR/*/versions/* services/*/migrations/versions/${NC}"
echo ""

View File

@@ -0,0 +1,23 @@
#!/bin/bash
# Script to remove imagePullSecrets from all Kubernetes manifests
# Run this from the repository root: ./infrastructure/kubernetes/remove-imagepullsecrets.sh
echo "Removing imagePullSecrets from all Kubernetes manifests..."
# Find all YAML files in base directory and remove imagePullSecrets
find infrastructure/kubernetes/base -name "*.yaml" -type f | while read file; do
# Create backup
cp "$file" "$file.bak"
# Remove imagePullSecrets and the following line (name: dockerhub-creds)
sed -i '/imagePullSecrets:/,+1d' "$file"
echo "Processed: $file"
done
echo ""
echo "✅ Done! Removed imagePullSecrets from all manifests"
echo "Backup files created with .bak extension"
echo ""
echo "Verify removal:"
grep -r "imagePullSecrets" infrastructure/kubernetes/base/ && echo "⚠️ WARNING: Some files still contain imagePullSecrets" || echo "✅ All imagePullSecrets removed successfully"

View File

@@ -0,0 +1,145 @@
#!/bin/bash
# Script to run the subscription creation integration test inside Kubernetes
# This script creates a test pod that runs the integration test
set -e
echo "🚀 Starting subscription creation integration test..."
# Check if there's already a test pod running
EXISTING_POD=$(kubectl get pod subscription-integration-test -n bakery-ia 2>/dev/null || echo "")
if [ -n "$EXISTING_POD" ]; then
echo "🧹 Cleaning up existing test pod..."
kubectl delete pod subscription-integration-test -n bakery-ia --wait=true
echo "✅ Existing pod cleaned up"
fi
# Determine the correct image to use by checking the existing tenant service deployment
IMAGE=$(kubectl get deployment tenant-service -n bakery-ia -o jsonpath='{.spec.template.spec.containers[0].image}')
if [ -z "$IMAGE" ]; then
echo "❌ Could not determine tenant service image. Is the tenant service deployed?"
exit 1
fi
echo "📦 Using image: $IMAGE"
# Create a test pod that runs the integration test with a simple command
echo "🔧 Creating test pod..."
kubectl run subscription-integration-test \
--image="$IMAGE" \
--namespace=bakery-ia \
--restart=Never \
--env="GATEWAY_URL=http://gateway-service:8000" \
--env="STRIPE_SECRET_KEY=$(kubectl get secret payment-secrets -n bakery-ia -o jsonpath='{.data.STRIPE_SECRET_KEY}' | base64 -d)" \
--command -- /bin/sh -c "
set -e
echo '🧪 Setting up test environment...' &&
cd /app &&
echo '📋 Installing test dependencies...' &&
pip install pytest pytest-asyncio httpx stripe --quiet &&
echo '✅ Dependencies installed' &&
echo '' &&
echo '🔧 Configuring test to use internal gateway service URL...' &&
# Backup original file before modification
cp tests/integration/test_subscription_creation_flow.py tests/integration/test_subscription_creation_flow.py.bak &&
# Update the test file to use the internal gateway service URL
sed -i 's|self.base_url = \"https://bakery-ia.local\"|self.base_url = \"http://gateway-service:8000\"|g' tests/integration/test_subscription_creation_flow.py &&
echo '✅ Test configured for internal Kubernetes networking' &&
echo '' &&
echo '🧪 Running subscription creation integration test...' &&
echo '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━' &&
python -m pytest tests/integration/test_subscription_creation_flow.py -v --tb=short -s --color=yes &&
TEST_RESULT=\$? &&
echo '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━' &&
echo '' &&
echo '📋 Restoring original test file...' &&
mv tests/integration/test_subscription_creation_flow.py.bak tests/integration/test_subscription_creation_flow.py &&
echo '✅ Original test file restored' &&
echo '' &&
if [ \$TEST_RESULT -eq 0 ]; then
echo '🎉 Integration test PASSED!'
else
echo '❌ Integration test FAILED!'
fi &&
exit \$TEST_RESULT
"
# Wait for the test pod to start
echo "⏳ Waiting for test pod to start..."
sleep 5
# Follow the logs in real-time
echo "📋 Following test execution logs..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
# Stream logs while the pod is running
kubectl logs -f subscription-integration-test -n bakery-ia 2>/dev/null || true
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
# Wait for the pod to complete with a timeout
echo "⏳ Waiting for test pod to complete..."
TIMEOUT=600 # 10 minutes timeout
COUNTER=0
while [ $COUNTER -lt $TIMEOUT ]; do
POD_STATUS=$(kubectl get pod subscription-integration-test -n bakery-ia -o jsonpath='{.status.phase}' 2>/dev/null)
if [ "$POD_STATUS" == "Succeeded" ] || [ "$POD_STATUS" == "Failed" ]; then
break
fi
sleep 2
COUNTER=$((COUNTER + 2))
done
if [ $COUNTER -ge $TIMEOUT ]; then
echo "⏰ Timeout waiting for test to complete after $TIMEOUT seconds"
echo "📋 Fetching final logs before cleanup..."
kubectl logs subscription-integration-test -n bakery-ia --tail=100
echo "🧹 Cleaning up test pod due to timeout..."
kubectl delete pod subscription-integration-test -n bakery-ia --wait=false
exit 1
fi
# Get the final status
POD_STATUS=$(kubectl get pod subscription-integration-test -n bakery-ia -o jsonpath='{.status.phase}')
CONTAINER_EXIT_CODE=$(kubectl get pod subscription-integration-test -n bakery-ia -o jsonpath='{.status.containerStatuses[0].state.terminated.exitCode}' 2>/dev/null || echo "unknown")
echo ""
echo "📊 Test Results:"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Pod Status: $POD_STATUS"
echo "Exit Code: $CONTAINER_EXIT_CODE"
# Determine if the test passed
if [ "$POD_STATUS" == "Succeeded" ] && [ "$CONTAINER_EXIT_CODE" == "0" ]; then
echo ""
echo "✅ Integration test PASSED!"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
RESULT=0
else
echo ""
echo "❌ Integration test FAILED!"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
# Show additional logs if failed
if [ "$POD_STATUS" == "Failed" ]; then
echo ""
echo "📋 Last 50 lines of logs:"
kubectl logs subscription-integration-test -n bakery-ia --tail=50
fi
RESULT=1
fi
# Clean up the test pod
echo ""
echo "🧹 Cleaning up test pod..."
kubectl delete pod subscription-integration-test -n bakery-ia --wait=false
echo "🏁 Integration test process completed!"
exit $RESULT

View File

@@ -0,0 +1,649 @@
#!/bin/bash
# Bakery IA HTTPS Setup Script
# This script sets up HTTPS with cert-manager and Let's Encrypt for local development
# Remove -e to handle errors more gracefully
set -u
echo "🔒 Setting up HTTPS for Bakery IA with cert-manager and Let's Encrypt"
echo "==============================================================="
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored output
print_status() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Check prerequisites
check_prerequisites() {
print_status "Checking prerequisites..."
# Check required tools
local missing_tools=()
if ! command -v kubectl &> /dev/null; then
missing_tools+=("kubectl")
fi
if ! command -v kind &> /dev/null; then
missing_tools+=("kind")
fi
if ! command -v skaffold &> /dev/null; then
missing_tools+=("skaffold")
fi
if ! command -v colima &> /dev/null; then
missing_tools+=("colima")
fi
# Report missing tools
if [ ${#missing_tools[@]} -ne 0 ]; then
print_error "Missing required tools: ${missing_tools[*]}"
print_error "Please install them with: brew install ${missing_tools[*]}"
exit 1
fi
# Check if Colima is running
if ! colima status --profile k8s-local &> /dev/null; then
print_warning "Colima is not running. Starting Colima..."
colima start --cpu 8 --memory 16 --disk 100 --runtime docker --profile k8s-local
if [ $? -ne 0 ]; then
print_error "Failed to start Colima. Please check your Docker installation."
exit 1
fi
print_success "Colima started successfully"
fi
# Check if cluster is running or exists
local cluster_exists=false
local cluster_running=false
# Check if Kind cluster exists
if kind get clusters | grep -q "bakery-ia-local"; then
cluster_exists=true
print_status "Kind cluster 'bakery-ia-local' already exists"
# Check if kubectl can connect to it
if kubectl cluster-info --context kind-bakery-ia-local &> /dev/null; then
cluster_running=true
print_success "Kubernetes cluster is running and accessible"
else
print_warning "Kind cluster exists but is not accessible via kubectl"
fi
fi
# Handle cluster creation/recreation
if [ "$cluster_exists" = true ] && [ "$cluster_running" = false ]; then
print_warning "Kind cluster exists but is not running. Recreating..."
kind delete cluster --name bakery-ia-local || true
cluster_exists=false
fi
if [ "$cluster_exists" = false ]; then
print_warning "Creating new Kind cluster..."
if [ ! -f "kind-config.yaml" ]; then
print_error "kind-config.yaml not found. Please ensure you're running this script from the project root."
exit 1
fi
if kind create cluster --config kind-config.yaml; then
print_success "Kind cluster created successfully"
else
print_error "Failed to create Kind cluster. Please check your Kind installation."
exit 1
fi
fi
# Ensure we're using the correct kubectl context
kubectl config use-context kind-bakery-ia-local || {
print_error "Failed to set kubectl context to kind-bakery-ia-local"
exit 1
}
print_success "Prerequisites check passed"
}
# Install cert-manager
install_cert_manager() {
print_status "Installing cert-manager..."
# Check if cert-manager is already installed
if kubectl get namespace cert-manager &> /dev/null; then
print_warning "cert-manager namespace already exists. Checking if installation is complete..."
# Check if pods are running
if kubectl get pods -n cert-manager | grep -q "Running"; then
print_success "cert-manager is already installed and running"
return 0
else
print_status "cert-manager exists but pods are not ready. Waiting..."
fi
else
# Install cert-manager
print_status "Installing cert-manager from official release..."
if kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.2/cert-manager.yaml; then
print_success "cert-manager installation started"
else
print_error "Failed to install cert-manager. Please check your internet connection and try again."
exit 1
fi
fi
# Wait for cert-manager namespace to be created
print_status "Waiting for cert-manager namespace..."
for i in {1..30}; do
if kubectl get namespace cert-manager &> /dev/null; then
break
fi
sleep 2
done
# Wait for cert-manager pods to be created
print_status "Waiting for cert-manager pods to be created..."
for i in {1..60}; do
if kubectl get pods -n cert-manager &> /dev/null && [ $(kubectl get pods -n cert-manager --no-headers | wc -l) -ge 3 ]; then
print_success "cert-manager pods created"
break
fi
print_status "Waiting for cert-manager pods... (attempt $i/60)"
sleep 5
done
# Wait for cert-manager pods to be ready
print_status "Waiting for cert-manager pods to be ready..."
# Use more reliable selectors for cert-manager components
local components=(
"app.kubernetes.io/name=cert-manager"
"app.kubernetes.io/name=cainjector"
"app.kubernetes.io/name=webhook"
)
local component_names=("cert-manager" "cert-manager-cainjector" "cert-manager-webhook")
for i in "${!components[@]}"; do
local selector="${components[$i]}"
local name="${component_names[$i]}"
print_status "Waiting for $name to be ready..."
# First check if pods exist with this selector
local pod_count=0
for attempt in {1..30}; do
pod_count=$(kubectl get pods -n cert-manager -l "$selector" --no-headers 2>/dev/null | wc -l)
if [ "$pod_count" -gt 0 ]; then
break
fi
sleep 2
done
if [ "$pod_count" -eq 0 ]; then
print_warning "No pods found for $name with selector $selector, trying alternative approach..."
# Fallback: wait for any pods containing the component name
if kubectl wait --for=condition=ready pod -n cert-manager --all --timeout=300s 2>/dev/null; then
print_success "All cert-manager pods are ready"
break
else
print_warning "$name pods not found, but continuing..."
continue
fi
fi
# Wait for the specific component to be ready
if kubectl wait --for=condition=ready pod -l "$selector" -n cert-manager --timeout=300s 2>/dev/null; then
print_success "$name is ready"
else
print_warning "$name is taking longer than expected. Checking status..."
kubectl get pods -n cert-manager -l "$selector" 2>/dev/null || true
# Continue anyway, sometimes it works despite timeout
print_warning "Continuing with setup. $name may still be starting..."
fi
done
# Final verification
if kubectl get pods -n cert-manager | grep -q "Running"; then
print_success "cert-manager installed successfully"
else
print_warning "cert-manager installation may not be complete. Current status:"
kubectl get pods -n cert-manager
print_status "Continuing with setup anyway..."
fi
}
# Install NGINX Ingress Controller
install_nginx_ingress() {
print_status "Installing NGINX Ingress Controller for Kind..."
# Check if NGINX Ingress is already installed
if kubectl get namespace ingress-nginx &> /dev/null; then
print_warning "NGINX Ingress Controller namespace already exists. Checking status..."
# Check if controller is running
if kubectl get pods -n ingress-nginx -l app.kubernetes.io/component=controller | grep -q "Running"; then
print_success "NGINX Ingress Controller is already running"
else
print_status "NGINX Ingress Controller exists but not ready. Waiting..."
kubectl wait --namespace ingress-nginx \
--for=condition=ready pod \
--selector=app.kubernetes.io/component=controller \
--timeout=300s 2>/dev/null || {
print_warning "Ingress controller taking longer than expected, but continuing..."
}
fi
else
# Install NGINX Ingress Controller for Kind (updated URL)
print_status "Installing NGINX Ingress Controller for Kind..."
if kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml; then
print_success "NGINX Ingress Controller installation started"
# Wait for ingress controller to be ready
print_status "Waiting for NGINX Ingress Controller to be ready..."
kubectl wait --namespace ingress-nginx \
--for=condition=ready pod \
--selector=app.kubernetes.io/component=controller \
--timeout=300s 2>/dev/null || {
print_warning "Ingress controller taking longer than expected, but continuing..."
}
else
print_error "Failed to install NGINX Ingress Controller"
exit 1
fi
fi
# Configure ingress for permanent localhost access
print_status "Configuring permanent localhost access..."
kubectl patch svc ingress-nginx-controller -n ingress-nginx -p '{"spec":{"type":"NodePort","ports":[{"name":"http","port":80,"targetPort":"http","nodePort":30080},{"name":"https","port":443,"targetPort":"https","nodePort":30443}]}}' || true
print_success "NGINX Ingress Controller configured successfully"
}
# Setup cluster issuers
setup_cluster_issuers() {
print_status "Setting up cluster issuers..."
# Check if cert-manager components exist
if [ ! -f "infrastructure/platform/cert-manager/cluster-issuer-staging.yaml" ]; then
print_error "cert-manager component files not found. Please ensure you're running this script from the project root."
exit 1
fi
# Apply cluster issuers
print_status "Applying cluster issuers..."
local issuer_files=(
"infrastructure/platform/cert-manager/cluster-issuer-staging.yaml"
"infrastructure/platform/cert-manager/local-ca-issuer.yaml"
"infrastructure/platform/cert-manager/cluster-issuer-production.yaml"
)
for issuer_file in "${issuer_files[@]}"; do
if [ -f "$issuer_file" ]; then
print_status "Applying $issuer_file..."
kubectl apply -f "$issuer_file" || {
print_warning "Failed to apply $issuer_file, but continuing..."
}
else
print_warning "$issuer_file not found, skipping..."
fi
done
# Wait for the issuers to be created
print_status "Waiting for cluster issuers to be ready..."
sleep 15
# Check if issuers are ready
print_status "Checking cluster issuer status..."
kubectl get clusterissuers 2>/dev/null || print_warning "No cluster issuers found yet"
# Verify that the local CA issuer is ready (if it exists)
if kubectl get clusterissuer local-ca-issuer &> /dev/null; then
for i in {1..10}; do
local issuer_ready=$(kubectl get clusterissuer local-ca-issuer -o jsonpath='{.status.conditions[0].type}' 2>/dev/null || echo "")
if [[ "$issuer_ready" == "Ready" ]]; then
print_success "Local CA issuer is ready"
break
fi
print_status "Waiting for local CA issuer to be ready... (attempt $i/10)"
sleep 10
done
else
print_warning "Local CA issuer not found, skipping readiness check"
fi
print_success "Cluster issuers configured successfully"
}
# Deploy the application with HTTPS using Skaffold
deploy_with_https() {
print_status "Deploying Bakery IA with HTTPS support using Skaffold..."
# Check if Skaffold is available
if ! command -v skaffold &> /dev/null; then
print_error "Skaffold is not installed. Please install skaffold first:"
print_error "brew install skaffold"
exit 1
fi
# Check if skaffold.yaml exists
if [ ! -f "skaffold.yaml" ]; then
print_error "skaffold.yaml not found. Please ensure you're running this script from the project root."
exit 1
fi
# Deploy with Skaffold (builds and deploys automatically with HTTPS support)
print_status "Building and deploying with Skaffold (dev profile includes HTTPS)..."
if skaffold run --profile=dev; then
print_success "Skaffold deployment started"
else
print_warning "Skaffold deployment had issues, but continuing..."
fi
# Wait for namespace to be created
print_status "Waiting for bakery-ia namespace..."
for i in {1..30}; do
if kubectl get namespace bakery-ia &> /dev/null; then
print_success "bakery-ia namespace found"
break
fi
sleep 2
done
# Check if namespace was created
if ! kubectl get namespace bakery-ia &> /dev/null; then
print_warning "bakery-ia namespace not found. Deployment may have failed."
return 0
fi
# Wait for deployments to be ready
print_status "Waiting for deployments to be ready..."
if kubectl wait --for=condition=available --timeout=600s deployment --all -n bakery-ia 2>/dev/null; then
print_success "All deployments are ready"
else
print_warning "Some deployments are taking longer than expected, but continuing..."
fi
# Verify ingress exists
if kubectl get ingress bakery-ingress -n bakery-ia &> /dev/null; then
print_success "HTTPS ingress configured successfully"
else
print_warning "Ingress not found, but continuing with setup..."
fi
print_success "Application deployed with HTTPS support using Skaffold"
}
# Check certificate status
check_certificates() {
print_status "Checking certificate status..."
# Wait for certificate to be issued
print_status "Waiting for certificates to be issued..."
# Check if certificate exists
for i in {1..12}; do
if kubectl get certificate bakery-ia-tls-cert -n bakery-ia &> /dev/null; then
print_success "Certificate found"
break
fi
print_status "Waiting for certificate to be created... (attempt $i/12)"
sleep 10
done
# Wait for certificate to be ready
for i in {1..20}; do
if kubectl get certificate bakery-ia-tls-cert -n bakery-ia -o jsonpath='{.status.conditions[0].type}' 2>/dev/null | grep -q "Ready"; then
print_success "Certificate is ready"
break
fi
print_status "Waiting for certificate to be ready... (attempt $i/20)"
sleep 15
done
echo ""
echo "📋 Certificate status:"
kubectl get certificates -n bakery-ia 2>/dev/null || print_warning "No certificates found"
echo ""
echo "🔍 Certificate details:"
kubectl describe certificate bakery-ia-tls-cert -n bakery-ia 2>/dev/null || print_warning "Certificate not found"
echo ""
echo "🔐 TLS secret status:"
kubectl get secret bakery-ia-tls-cert -n bakery-ia 2>/dev/null || print_warning "TLS secret not found"
}
# Update hosts file
update_hosts_file() {
print_status "Checking hosts file configuration..."
# Get the external IP for Kind
EXTERNAL_IP="127.0.0.1"
# Check if entries exist in hosts file
if ! grep -q "bakery-ia.local" /etc/hosts 2>/dev/null; then
print_warning "Adding entries to /etc/hosts file for named host access..."
# Ask for user permission
read -p "Do you want to add entries to /etc/hosts for named host access? (y/N): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
# Add hosts entries with proper error handling
{
echo "$EXTERNAL_IP bakery-ia.local"
echo "$EXTERNAL_IP api.bakery-ia.local"
echo "$EXTERNAL_IP monitoring.bakery-ia.local"
} | sudo tee -a /etc/hosts > /dev/null
if [ $? -eq 0 ]; then
print_success "Hosts file entries added successfully"
else
print_error "Failed to update hosts file. You may need to add entries manually."
fi
else
print_warning "Skipping hosts file update. You can still access via https://localhost"
fi
else
print_success "Hosts file entries already exist"
fi
echo ""
print_status "Available access methods:"
echo " 🌐 Primary: https://localhost (no hosts file needed)"
echo " 🏷️ Named: https://bakery-ia.local (requires hosts file)"
echo " 🔗 API: https://localhost/api or https://api.bakery-ia.local"
}
# Export CA certificate for browser trust
export_ca_certificate() {
print_status "Exporting CA certificate for browser trust..."
# Wait for CA certificate to be created
for i in {1..10}; do
if kubectl get secret local-ca-key-pair -n cert-manager &> /dev/null; then
print_success "CA certificate secret found"
break
fi
print_status "Waiting for CA certificate secret... (attempt $i/10)"
sleep 10
done
# Extract the CA certificate
if kubectl get secret local-ca-key-pair -n cert-manager &> /dev/null; then
if kubectl get secret local-ca-key-pair -n cert-manager -o jsonpath='{.data.tls\.crt}' | base64 -d > bakery-ia-ca.crt 2>/dev/null; then
print_success "CA certificate exported as 'bakery-ia-ca.crt'"
# Make the certificate file readable
chmod 644 bakery-ia-ca.crt
else
print_warning "Failed to extract CA certificate from secret"
fi
print_warning "To trust this certificate and remove browser warnings:"
echo ""
echo "📱 macOS:"
echo " 1. Double-click 'bakery-ia-ca.crt' to open Keychain Access"
echo " 2. Find 'bakery-ia-local-ca' in the certificates list"
echo " 3. Double-click it and set to 'Always Trust'"
echo ""
echo "🐧 Linux:"
echo " sudo cp bakery-ia-ca.crt /usr/local/share/ca-certificates/"
echo " sudo update-ca-certificates"
echo ""
echo "🪟 Windows:"
echo " 1. Double-click 'bakery-ia-ca.crt'"
echo " 2. Click 'Install Certificate'"
echo " 3. Choose 'Trusted Root Certification Authorities'"
echo ""
else
print_warning "CA certificate secret not found. HTTPS will work but with browser warnings."
print_warning "You can still access the application at https://localhost"
fi
}
# Display access information
display_access_info() {
print_success "🎉 HTTPS setup completed!"
echo ""
echo "🌐 Access your application at:"
echo " Primary: https://localhost"
echo " API: https://localhost/api"
echo " Named Host: https://bakery-ia.local (if hosts file updated)"
echo " API Named: https://api.bakery-ia.local (if hosts file updated)"
echo ""
echo "🛠️ Useful commands:"
echo " 📋 Check status: kubectl get all -n bakery-ia"
echo " 🔍 Check ingress: kubectl get ingress -n bakery-ia"
echo " 📜 Check certificates: kubectl get certificates -n bakery-ia"
echo " 📝 View service logs: kubectl logs -f deployment/<service-name> -n bakery-ia"
echo " 🚀 Development mode: skaffold dev --profile=dev"
echo " 🧹 Clean up: skaffold delete --profile=dev"
echo " 🔄 Restart service: kubectl rollout restart deployment/<service-name> -n bakery-ia"
echo ""
echo "🔧 Troubleshooting:"
echo " 🩺 Get events: kubectl get events -n bakery-ia --sort-by='.firstTimestamp'"
echo " 🔍 Describe pod: kubectl describe pod <pod-name> -n bakery-ia"
echo " 📊 Resource usage: kubectl top pods -n bakery-ia"
echo " 🔐 Certificate details: kubectl describe certificate bakery-ia-tls-cert -n bakery-ia"
echo ""
if [ -f "bakery-ia-ca.crt" ]; then
print_warning "📋 Next steps:"
echo " 1. Import 'bakery-ia-ca.crt' into your browser to remove certificate warnings"
echo " 2. Access https://localhost to verify the setup"
echo " 3. Run 'skaffold dev --profile=dev' for development with hot-reload"
else
print_warning "⚠️ Note: You may see certificate warnings until the CA certificate is properly configured"
fi
echo ""
print_status "🎯 The application is now ready for secure development!"
}
# Check current cert-manager status for debugging
check_current_cert_manager_status() {
print_status "Checking current cert-manager status..."
if kubectl get namespace cert-manager &> /dev/null; then
echo ""
echo "📋 Current cert-manager pods status:"
kubectl get pods -n cert-manager
echo ""
echo "🔍 cert-manager deployments:"
kubectl get deployments -n cert-manager
# Check for any pending or failed pods
local failed_pods=$(kubectl get pods -n cert-manager --field-selector=status.phase!=Running --no-headers 2>/dev/null | wc -l)
if [ "$failed_pods" -gt 0 ]; then
echo ""
print_warning "Found $failed_pods non-running pods. Details:"
kubectl get pods -n cert-manager --field-selector=status.phase!=Running
fi
echo ""
else
print_status "cert-manager namespace not found. Will install fresh."
fi
}
# Cleanup function for failed installations
cleanup_on_failure() {
print_warning "Cleaning up due to failure..."
# Optional cleanup - ask user
read -p "Do you want to clean up the Kind cluster and start fresh? (y/N): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
print_status "Cleaning up Kind cluster..."
kind delete cluster --name bakery-ia-local || true
print_success "Cleanup completed. You can run the script again."
else
print_status "Keeping existing setup. You can continue manually or run the script again."
fi
}
# Trap function to handle script interruption
trap 'echo ""; print_warning "Script interrupted. Partial setup may be present."; cleanup_on_failure; exit 1' INT TERM
# Main execution
main() {
echo "Starting HTTPS setup for Bakery IA..."
# Set error handling for individual steps
local step_failed=false
check_prerequisites || { step_failed=true; }
if [ "$step_failed" = false ]; then
check_current_cert_manager_status || { step_failed=true; }
fi
if [ "$step_failed" = false ]; then
install_cert_manager || { step_failed=true; }
fi
if [ "$step_failed" = false ]; then
install_nginx_ingress || { step_failed=true; }
fi
if [ "$step_failed" = false ]; then
setup_cluster_issuers || { step_failed=true; }
fi
if [ "$step_failed" = false ]; then
deploy_with_https || { step_failed=true; }
fi
if [ "$step_failed" = false ]; then
check_certificates || { step_failed=true; }
fi
if [ "$step_failed" = false ]; then
update_hosts_file || { step_failed=true; }
fi
if [ "$step_failed" = false ]; then
export_ca_certificate || { step_failed=true; }
fi
if [ "$step_failed" = false ]; then
display_access_info
print_success "Setup completed successfully! 🚀"
else
print_error "Setup failed at one or more steps. Check the output above for details."
cleanup_on_failure
exit 1
fi
}
# Run main function
main "$@"

View File

@@ -0,0 +1,154 @@
#!/bin/bash
# Script to tag and push all Bakery IA images to a container registry
# Usage: ./tag-and-push-images.sh [REGISTRY_PREFIX] [TAG]
# Example: ./tag-and-push-images.sh myuser/bakery v1.0.0
set -e
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Configuration
REGISTRY_PREFIX="${1:-}"
TAG="${2:-latest}"
if [ -z "$REGISTRY_PREFIX" ]; then
echo -e "${RED}Error: Registry prefix required${NC}"
echo "Usage: $0 REGISTRY_PREFIX [TAG]"
echo ""
echo "Examples:"
echo " Docker Hub: $0 myusername/bakery v1.0.0"
echo " GitHub: $0 ghcr.io/myorg/bakery v1.0.0"
echo " MicroK8s: $0 YOUR_VPS_IP:32000/bakery v1.0.0"
exit 1
fi
# List of all services
SERVICES=(
"gateway"
"dashboard"
"auth-service"
"tenant-service"
"training-service"
"forecasting-service"
"sales-service"
"external-service"
"notification-service"
"inventory-service"
"recipes-service"
"suppliers-service"
"pos-service"
"orders-service"
"production-service"
"procurement-service"
"orchestrator-service"
"alert-processor"
"ai-insights-service"
"demo-session-service"
"distribution-service"
)
echo -e "${GREEN}========================================${NC}"
echo -e "${GREEN}Bakery IA - Image Tagging and Push${NC}"
echo -e "${GREEN}========================================${NC}"
echo ""
echo "Registry: $REGISTRY_PREFIX"
echo "Tag: $TAG"
echo ""
# Function to tag image
tag_image() {
local service=$1
local local_name="bakery/${service}"
local remote_name="${REGISTRY_PREFIX}-${service}:${TAG}"
echo -e "${YELLOW}Tagging ${local_name} -> ${remote_name}${NC}"
if docker tag "$local_name" "$remote_name"; then
echo -e "${GREEN}✓ Tagged $service${NC}"
return 0
else
echo -e "${RED}✗ Failed to tag $service${NC}"
return 1
fi
}
# Function to push image
push_image() {
local service=$1
local remote_name="${REGISTRY_PREFIX}-${service}:${TAG}"
echo -e "${YELLOW}Pushing ${remote_name}${NC}"
if docker push "$remote_name"; then
echo -e "${GREEN}✓ Pushed $service${NC}"
return 0
else
echo -e "${RED}✗ Failed to push $service${NC}"
return 1
fi
}
# Check if user is logged in to registry
echo -e "${YELLOW}Checking registry authentication...${NC}"
if ! docker info > /dev/null 2>&1; then
echo -e "${RED}Error: Docker daemon not running${NC}"
exit 1
fi
echo -e "${GREEN}✓ Docker is running${NC}"
echo ""
# Ask for confirmation
echo -e "${YELLOW}This will tag and push ${#SERVICES[@]} images.${NC}"
read -p "Continue? (yes/no): " confirm
if [ "$confirm" != "yes" ]; then
echo "Cancelled."
exit 0
fi
echo ""
echo -e "${GREEN}Starting image tagging and push...${NC}"
echo ""
# Track success/failure
SUCCESS_COUNT=0
FAILED_SERVICES=()
# Tag and push all images
for service in "${SERVICES[@]}"; do
if tag_image "$service" && push_image "$service"; then
((SUCCESS_COUNT++))
else
FAILED_SERVICES+=("$service")
fi
echo ""
done
# Summary
echo -e "${GREEN}========================================${NC}"
echo -e "${GREEN}Summary${NC}"
echo -e "${GREEN}========================================${NC}"
echo ""
echo "Successfully pushed: $SUCCESS_COUNT/${#SERVICES[@]}"
if [ ${#FAILED_SERVICES[@]} -gt 0 ]; then
echo -e "${RED}Failed services:${NC}"
for service in "${FAILED_SERVICES[@]}"; do
echo -e "${RED} - $service${NC}"
done
exit 1
else
echo -e "${GREEN}All images pushed successfully!${NC}"
echo ""
echo "Next steps:"
echo "1. Update image names in infrastructure/environments/prod/k8s-manifests/kustomization.yaml"
echo "2. Deploy to production: kubectl apply -k infrastructure/environments/prod/k8s-manifests"
fi
echo ""