#!/bin/bash # Local CI/CD Testing Script # Simulates the CI/CD pipeline but builds Docker image locally # Use this for local development and testing without Gitea set -eu echo "🚀 Local CI/CD Testing" echo "======================" echo "" # 1. Setup echo "1. Setting up environment..." if ! command -v go >/dev/null 2>&1; then echo "❌ Go not found. Please install Go 1.26.1+" exit 1 fi # Assume Docker is available (required for this workflow) if ! command -v docker >/dev/null 2>&1; then echo "❌ Docker is required for this CI/CD workflow" echo "Please install Docker and Docker Compose plugin" exit 1 fi # Check for docker compose plugin if ! docker compose version >/dev/null 2>&1; then echo "âš ī¸ Docker Compose plugin not found. Installing..." sudo apt-get update && sudo apt-get install -y docker-compose-plugin fi echo "✅ Environment ready" echo "" # 2. Calculate dependency hash (match CI workflow) echo "2. Calculating dependency hash..." # Use shasum on macOS, sha256sum on Linux if command -v sha256sum >/dev/null 2>&1; then export DEPS_HASH=$(sha256sum go.mod go.sum | sha256sum | cut -d' ' -f1 | head -c 12) else export DEPS_HASH=$(shasum -a 256 go.mod go.sum | shasum -a 256 | cut -d' ' -f1 | head -c 12) fi echo "Dependency hash: $DEPS_HASH" echo "✅ Dependency hash calculated" echo "" # 3. Check for Docker cache echo "3. Checking for Docker build cache..." IMAGE_NAME="gitea.arcodange.lab/arcodange/dance-lessons-coach-build-cache:$DEPS_HASH" # Try to pull the cache image if docker pull "$IMAGE_NAME" >/dev/null 2>&1; then echo "✅ Cache hit - using existing build cache" USE_DOCKER_CACHE=true else echo "âš ī¸ Cache miss - will build without cache" USE_DOCKER_CACHE=false fi echo "" # 4. Start PostgreSQL with Docker Compose echo "4. Starting PostgreSQL..." docker compose -f docker-compose.yml up -d postgres # Wait for PostgreSQL to be ready echo "Waiting for PostgreSQL to be ready..." for i in {1..30}; do if docker exec dance-lessons-coach-postgres pg_isready -U postgres; then echo "✅ PostgreSQL is ready!" break fi echo "Waiting for PostgreSQL... ($i/30)" sleep 2 done # Set PostgreSQL environment variables for BDD tests export DLC_DATABASE_HOST="localhost" # PostgreSQL port is mapped to host export DLC_DATABASE_PORT=5432 export DLC_DATABASE_USER=postgres export DLC_DATABASE_PASSWORD=postgres export DLC_DATABASE_NAME=dance_lessons_coach_bdd_test export DLC_DATABASE_SSL_MODE=disable echo "" # 5. Install dependencies if [ "$USE_DOCKER_CACHE" = true ]; then echo "5. Checking dependencies..." echo "✅ Using pre-installed dependencies from Docker cache" else echo "5. Installing dependencies..." go mod tidy fi echo "✅ Dependencies ready" echo "" # 6. Generate Swagger Docs if [ "$USE_DOCKER_CACHE" = true ]; then echo "6. Generating Swagger documentation..." echo "Running in Docker container..." docker run --rm \ --network dance-lessons-coach-network \ -v "$(pwd):/workspace" \ -w /workspace/pkg/server \ "$IMAGE_NAME" \ sh -c "go generate" else echo "6. Generating Swagger documentation..." echo "Running natively..." cd pkg/server && go generate cd ../.. fi echo "✅ Swagger documentation generated" echo "" # 7. Build and test if [ "$USE_DOCKER_CACHE" = true ]; then echo "7. Building and testing..." echo "Running in Docker container..." docker run --rm \ --network dance-lessons-coach-network \ -v "$(pwd):/workspace" \ -w /workspace \ "$IMAGE_NAME" \ sh -c "go build ./..." else echo "7. Building and testing..." echo "Running natively..." go build ./... fi echo "✅ Code compiled successfully" if [ "$USE_DOCKER_CACHE" = true ]; then echo "Running in Docker container with PostgreSQL..." docker run --rm \ --network dance-lessons-coach-network \ -v "$(pwd):/workspace" \ -w /workspace \ -e DLC_DATABASE_HOST=dance-lessons-coach-postgres \ -e DLC_DATABASE_PORT=5432 \ -e DLC_DATABASE_USER=postgres \ -e DLC_DATABASE_PASSWORD=postgres \ -e DLC_DATABASE_NAME=dance_lessons_coach_bdd_test \ -e DLC_DATABASE_SSL_MODE=disable \ "$IMAGE_NAME" \ sh -c "go test ./... -coverprofile=coverage.out -v && go tool cover -func=coverage.out > coverage.txt" else echo "Running natively with Docker Compose PostgreSQL..." go test ./... -coverprofile=coverage.out -v go tool cover -func=coverage.out > coverage.txt fi echo "✅ Tests passed" echo "" # 8. Build binaries if [ "$USE_DOCKER_CACHE" = true ]; then echo "8. Building binaries..." echo "Running in Docker container..." docker run --rm \ --network dance-lessons-coach-network \ -v "$(pwd):/workspace" \ -w /workspace \ "$IMAGE_NAME" \ sh -c "./scripts/build.sh" else echo "8. Building binaries..." echo "Running natively..." ./scripts/build.sh fi echo "✅ Binaries built" ls -la bin/ echo "" # 9. Version bump simulation echo "9. Version bump simulation..." LAST_COMMIT=$(git log -1 --pretty=%B | head -1) echo "Last commit: $LAST_COMMIT" if echo "$LAST_COMMIT" | grep -q "^feat:"; then echo "đŸŽ¯ Feature commit detected - would bump MINOR version" echo "Run: ./scripts/version-bump.sh minor" elif echo "$LAST_COMMIT" | grep -q "^fix:"; then echo "🐛 Fix commit detected - would bump PATCH version" echo "Run: ./scripts/version-bump.sh patch" elif echo "$LAST_COMMIT" | grep -q "BREAKING CHANGE"; then echo "đŸ’Ĩ Breaking change detected - would bump MAJOR version" echo "Run: ./scripts/version-bump.sh major" else echo "â­ī¸ No automatic version bump needed" fi # Show current version source VERSION CURRENT_VERSION="$MAJOR.$MINOR.$PATCH${PRERELEASE:+-$PRERELEASE}" echo "📊 Current version: $CURRENT_VERSION" echo "" # 10. Local Docker build instructions echo "đŸŗ LOCAL DOCKER BUILD INSTRUCTIONS" echo "================================" echo "" echo "1. Build Docker image locally (development):" echo " docker build -t dance-lessons-coach:$CURRENT_VERSION ." echo "" echo "2. Build production image using docker/Dockerfile.prod:" echo " # Note: Local docker/Dockerfile.prod uses 'latest' tag for testing" echo " docker build -t dance-lessons-coach-prod:$CURRENT_VERSION -f docker/Dockerfile.prod ." echo " # For CI/CD, the workflow generates correct docker/Dockerfile.prod with dependency hash" echo "" echo "3. Compare image sizes:" echo " docker images | grep dance-lessons-coach" echo "" echo "4. Tag the image:" echo " docker tag dance-lessons-coach:$CURRENT_VERSION dance-lessons-coach:latest" echo "" echo "5. Test the local image (check port availability first):" echo " docker run -d -p 8080:8080 dance-lessons-coach:$CURRENT_VERSION" echo " # Or use alternative port if 8080 is in use:" echo " docker run -d -p 8081:8080 dance-lessons-coach:$CURRENT_VERSION" echo "" echo "6. Branch-specific container naming (recommended):" echo " BRANCH=\"$(git rev-parse --abbrev-ref HEAD | tr '/' '-')\"" echo " docker run -d -p 8080:8080 --name dance-lessons-coach-\"$BRANCH\" dance-lessons-coach:$CURRENT_VERSION" echo "" echo "7. Test API endpoints:" echo " curl http://localhost:8080/api/health" echo " curl http://localhost:8080/api/v1/greet/YourName" echo "" echo "8. Clean up:" echo " docker stop && docker rm " echo "" echo "💡 Tip: Use 'docker images' to see your built images" echo "💡 Use 'docker ps' to see running containers" echo "" # Ask if user wants to build Docker image now read -p "🚀 Do you want to build the Docker image now? (y/n): " -n 1 -r echo "" if [[ $REPLY =~ ^[Yy]$ ]]; then echo "đŸŗ Building Docker image..." read -p "📋 Build (d)development or (p)production image? [d/p]: " -n 1 -r echo "" if [[ $REPLY =~ ^[Pp]$ ]]; then echo "đŸ—ī¸ Building production image with docker/Dockerfile.prod..." docker build -t dance-lessons-coach-prod:$CURRENT_VERSION -f docker/Dockerfile.prod . docker tag dance-lessons-coach-prod:$CURRENT_VERSION dance-lessons-coach-prod:latest echo "✅ Production Docker image built: dance-lessons-coach-prod:$CURRENT_VERSION" CONTAINER_IMAGE="dance-lessons-coach-prod:$CURRENT_VERSION" else echo "đŸ—ī¸ Building development image with Dockerfile..." docker build -t dance-lessons-coach:$CURRENT_VERSION . docker tag dance-lessons-coach:$CURRENT_VERSION dance-lessons-coach:latest echo "✅ Development Docker image built: dance-lessons-coach:$CURRENT_VERSION" CONTAINER_IMAGE="dance-lessons-coach:$CURRENT_VERSION" fi echo "" # Check if port 8080 is available echo "🔍 Checking port availability..." if lsof -i :8080 > /dev/null 2>&1; then echo "âš ī¸ Port 8080 is already in use" read -p "🚀 Do you want to use a different port? (y/n): " -n 1 -r echo "" if [[ $REPLY =~ ^[Yy]$ ]]; then read -p "Enter port number (e.g., 8081): " CUSTOM_PORT echo "" PORT=$CUSTOM_PORT else echo "â„šī¸ Using port 8080 anyway (may fail if service is running)" PORT=8080 fi else echo "✅ Port 8080 is available" PORT=8080 fi read -p "🚀 Do you want to run the container now on port $PORT? (y/n): " -n 1 -r echo "" if [[ $REPLY =~ ^[Yy]$ ]]; then # Get current branch name for container naming BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD | tr '/' '-') CONTAINER_NAME="dance-lessons-coach-$BRANCH_NAME" echo "đŸŗ Preparing container '$CONTAINER_NAME' on port $PORT..." # Remove existing container if it exists if docker ps -a --format '{{.Names}}' | grep -q "^$CONTAINER_NAME$"; then echo "âš ī¸ Container '$CONTAINER_NAME' already exists - removing it..." docker stop "$CONTAINER_NAME" > /dev/null 2>&1 || true docker rm "$CONTAINER_NAME" > /dev/null 2>&1 || true echo "✅ Old container removed" fi # Also remove the generic test container if it exists if docker ps -a --format '{{.Names}}' | grep -q "^dance-lessons-coach-test$"; then echo "âš ī¸ Generic test container exists - removing it..." docker stop dance-lessons-coach-test > /dev/null 2>&1 || true docker rm dance-lessons-coach-test > /dev/null 2>&1 || true echo "✅ Old generic container removed" fi echo "đŸŗ Starting container '$CONTAINER_NAME' on port $PORT..." docker run -d -p $PORT:8080 --name "$CONTAINER_NAME" "$CONTAINER_IMAGE" echo "✅ Container '$CONTAINER_NAME' started on port $PORT" echo "" # Wait for container to be ready echo "🕒 Waiting for container to be ready..." MAX_ATTEMPTS=10 ATTEMPT=1 READY=false while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do if curl -s http://localhost:$PORT/api/health | grep -q "healthy"; then READY=true break fi sleep 1 ATTEMPT=$((ATTEMPT + 1)) echo "🕒 Attempt $ATTEMPT/$MAX_ATTEMPTS..." done if [ "$READY" = true ]; then echo "✅ Container is ready!" else echo "❌ Container failed to start properly" echo "📋 Container logs:" docker logs dance-lessons-coach-test echo "" echo "💡 Check container status with: docker ps -a" echo "💡 View full logs with: docker logs dance-lessons-coach-test" continue # Skip endpoint testing fi echo "📋 Testing endpoints..." if curl -s http://localhost:$PORT/api/health | grep -q "healthy"; then echo "✅ Health check passed" else echo "❌ Health check failed" fi if curl -s http://localhost:$PORT/api/v1/greet/ | grep -q "Hello"; then echo "✅ Greet endpoint working" else echo "❌ Greet endpoint failed" fi echo "" echo "📖 Swagger UI available at: http://localhost:$PORT/swagger/" echo "💡 Press Ctrl+C to stop the container when done" echo " Or run: docker stop $CONTAINER_NAME && docker rm $CONTAINER_NAME" fi fi echo "" echo "✅ LOCAL CI/CD TEST COMPLETE" echo "===========================" echo "" echo "📋 What was tested:" echo " ✅ Dependency hash calculation (matching CI workflow)" echo " ✅ Docker cache detection and usage" echo " ✅ PostgreSQL service with Docker Compose" echo " ✅ Go dependencies installation" echo " ✅ Swagger documentation generation" echo " ✅ Code compilation" echo " ✅ Unit tests with coverage" echo " ✅ Binary build" echo " ✅ Version bump simulation" echo " ✅ Docker build (development and/or production if chosen)" echo "" echo "đŸŽ¯ When ready for production:" echo " Push to main branch to trigger full CI/CD pipeline" echo " Docker image will be built and pushed to Gitea Container Registry" echo "" echo "💡 Local testing complete! Your changes are ready for CI/CD." echo "💡 This script now matches the Gitea workflow structure and behavior." # âš ī¸ IMPORTANT: Local Dockerfile.prod uses 'latest' tag for testing only # ✅ CI/CD workflow generates correct Dockerfile.prod with dependency hash