feat: release build
This commit is contained in:
parent
8f677f62b8
commit
89026e3460
@ -18,7 +18,8 @@
|
||||
"Bash(rm:*)",
|
||||
"Bash(curl:*)",
|
||||
"Bash(docker-compose up:*)",
|
||||
"Bash(dotnet build:*)"
|
||||
"Bash(dotnet build:*)",
|
||||
"Bash(chmod:*)"
|
||||
]
|
||||
},
|
||||
"enableAllProjectMcpServers": false
|
||||
|
||||
166
.gitea/workflows/release-deploy.yml
Normal file
166
.gitea/workflows/release-deploy.yml
Normal file
@ -0,0 +1,166 @@
|
||||
name: Release Deployment Pipeline
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'Release/*'
|
||||
|
||||
env:
|
||||
REGISTRY: registry.redecarneir.us
|
||||
IMAGE_NAME: bcards
|
||||
MONGODB_HOST: 192.168.0.100:27017
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Run Tests
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup .NET 8
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build solution
|
||||
run: dotnet build --no-restore --configuration Release
|
||||
|
||||
- name: Run unit tests
|
||||
run: dotnet test --no-build --configuration Release --verbosity normal --collect:"XPlat Code Coverage"
|
||||
|
||||
- name: Test MongoDB connection
|
||||
run: |
|
||||
echo "Testing MongoDB connection to $MONGODB_HOST"
|
||||
timeout 10 bash -c "</dev/tcp/192.168.0.100/27017" && echo "MongoDB connection successful" || echo "MongoDB connection failed"
|
||||
|
||||
build-and-deploy:
|
||||
name: Build Multi-Arch Image and Deploy
|
||||
runs-on: ubuntu-latest
|
||||
needs: test
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
driver-opts: network=host
|
||||
|
||||
- name: Extract branch name and version
|
||||
id: extract_branch
|
||||
run: |
|
||||
BRANCH_NAME=${GITHUB_REF#refs/heads/}
|
||||
VERSION=${BRANCH_NAME#Release/}
|
||||
COMMIT_SHA=${GITHUB_SHA::7}
|
||||
echo "branch=$BRANCH_NAME" >> $GITHUB_OUTPUT
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "commit=$COMMIT_SHA" >> $GITHUB_OUTPUT
|
||||
echo "tag=$VERSION-$COMMIT_SHA" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build and push multi-arch Docker image
|
||||
run: |
|
||||
echo "Building multi-arch image for platforms: linux/amd64,linux/arm64"
|
||||
docker buildx build \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--file Dockerfile.release \
|
||||
--tag $REGISTRY/$IMAGE_NAME:${{ steps.extract_branch.outputs.tag }} \
|
||||
--tag $REGISTRY/$IMAGE_NAME:${{ steps.extract_branch.outputs.version }}-latest \
|
||||
--tag $REGISTRY/$IMAGE_NAME:release-latest \
|
||||
--push \
|
||||
--build-arg BUILDPLATFORM=linux/amd64 \
|
||||
--build-arg VERSION=${{ steps.extract_branch.outputs.version }} \
|
||||
--build-arg COMMIT=${{ steps.extract_branch.outputs.commit }} \
|
||||
.
|
||||
|
||||
- name: Deploy to staging environment
|
||||
run: |
|
||||
echo "Deploying to staging environment..."
|
||||
|
||||
# Create deployment directory
|
||||
sudo mkdir -p /opt/bcards-staging
|
||||
|
||||
# Copy docker-compose file
|
||||
sudo cp docker-compose.staging.yml /opt/bcards-staging/
|
||||
|
||||
# Set environment variables
|
||||
sudo tee /opt/bcards-staging/.env > /dev/null <<EOF
|
||||
IMAGE_TAG=${{ steps.extract_branch.outputs.tag }}
|
||||
MONGODB_CONNECTION_STRING=mongodb://$MONGODB_HOST/BCardsDB
|
||||
ASPNETCORE_ENVIRONMENT=Release
|
||||
EOF
|
||||
|
||||
# Run deployment script
|
||||
chmod +x scripts/deploy-release.sh
|
||||
sudo ./scripts/deploy-release.sh ${{ steps.extract_branch.outputs.tag }}
|
||||
|
||||
- name: Health check and validation
|
||||
run: |
|
||||
echo "Running health checks..."
|
||||
|
||||
# Wait for application to start
|
||||
sleep 30
|
||||
|
||||
# Test application health
|
||||
for i in {1..10}; do
|
||||
if curl -f http://localhost:8090/health > /dev/null 2>&1; then
|
||||
echo "Application health check passed"
|
||||
break
|
||||
fi
|
||||
echo "Health check attempt $i failed, retrying in 10 seconds..."
|
||||
sleep 10
|
||||
if [ $i -eq 10 ]; then
|
||||
echo "Health check failed after 10 attempts"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Test MongoDB connectivity from application
|
||||
chmod +x scripts/test-mongodb-connection.sh
|
||||
./scripts/test-mongodb-connection.sh
|
||||
|
||||
- name: Deployment notification
|
||||
run: |
|
||||
echo "🚀 Deployment Status: SUCCESS"
|
||||
echo "📦 Image: $REGISTRY/$IMAGE_NAME:${{ steps.extract_branch.outputs.tag }}"
|
||||
echo "🌐 Environment: Staging"
|
||||
echo "🔗 MongoDB: $MONGODB_HOST"
|
||||
echo "🏗️ Architecture: Multi-arch (linux/amd64, linux/arm64)"
|
||||
echo "📋 Branch: ${{ steps.extract_branch.outputs.branch }}"
|
||||
echo "🆔 Commit: ${{ steps.extract_branch.outputs.commit }}"
|
||||
|
||||
rollback:
|
||||
name: Rollback on Failure
|
||||
runs-on: ubuntu-latest
|
||||
needs: [test, build-and-deploy]
|
||||
if: failure()
|
||||
|
||||
steps:
|
||||
- name: Rollback deployment
|
||||
run: |
|
||||
echo "🚨 Deployment failed, initiating rollback..."
|
||||
|
||||
# Stop current containers
|
||||
cd /opt/bcards-staging
|
||||
sudo docker-compose down
|
||||
|
||||
# Restore previous version if exists
|
||||
if [ -f .env.backup ]; then
|
||||
sudo mv .env.backup .env
|
||||
sudo docker-compose up -d
|
||||
echo "✅ Rollback completed to previous version"
|
||||
else
|
||||
echo "❌ No previous version found for rollback"
|
||||
fi
|
||||
|
||||
- name: Failure notification
|
||||
run: |
|
||||
echo "❌ Deployment Status: FAILED"
|
||||
echo "🔄 Rollback: Initiated"
|
||||
echo "📋 Branch: ${GITHUB_REF#refs/heads/}"
|
||||
echo "🆔 Commit: ${GITHUB_SHA::7}"
|
||||
10
BCards.sln
10
BCards.sln
@ -6,6 +6,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BCards.Web", "src\BCards.We
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BCards.IntegrationTests", "src\BCards.IntegrationTests\BCards.IntegrationTests.csproj", "{8F9E4C7D-2A3B-4E5F-9C8D-1B2A3E4F5C6D}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scripts", "Scripts", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
scripts\deploy-release.sh = scripts\deploy-release.sh
|
||||
scripts\init-mongo.js = scripts\init-mongo.js
|
||||
scripts\test-mongodb-connection.sh = scripts\test-mongodb-connection.sh
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@ -24,4 +31,7 @@ Global
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {3DA87F09-8B78-450D-9EF8-A0C0E02F0E04}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
123
Dockerfile.release
Normal file
123
Dockerfile.release
Normal file
@ -0,0 +1,123 @@
|
||||
# Dockerfile.release - Multi-architecture build for Release environment
|
||||
# Supports: linux/amd64, linux/arm64
|
||||
|
||||
ARG BUILDPLATFORM=linux/amd64
|
||||
ARG TARGETPLATFORM
|
||||
ARG VERSION=unknown
|
||||
ARG COMMIT=unknown
|
||||
|
||||
# Base runtime image with multi-arch support
|
||||
FROM --platform=$TARGETPLATFORM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
|
||||
WORKDIR /app
|
||||
EXPOSE 8080
|
||||
EXPOSE 8443
|
||||
|
||||
# Install dependencies based on target platform
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
libgdiplus \
|
||||
curl \
|
||||
ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& apt-get clean
|
||||
|
||||
# Create application directories
|
||||
RUN mkdir -p /app/uploads /app/logs \
|
||||
&& chmod 755 /app/uploads /app/logs
|
||||
|
||||
# Build stage - use build platform for compilation
|
||||
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||
ARG TARGETPLATFORM
|
||||
ARG VERSION
|
||||
ARG COMMIT
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
# Copy project files and restore dependencies
|
||||
COPY ["src/BCards.Web/BCards.Web.csproj", "src/BCards.Web/"]
|
||||
RUN dotnet restore "src/BCards.Web/BCards.Web.csproj" \
|
||||
--runtime $(echo $TARGETPLATFORM | tr '/' '-')
|
||||
|
||||
# Copy source code
|
||||
COPY . .
|
||||
WORKDIR "/src/src/BCards.Web"
|
||||
|
||||
# Build application with Release configuration
|
||||
RUN dotnet build "BCards.Web.csproj" \
|
||||
-c Release \
|
||||
-o /app/build \
|
||||
--no-restore \
|
||||
--runtime $(echo $TARGETPLATFORM | tr '/' '-') \
|
||||
-p:Version=$VERSION \
|
||||
-p:InformationalVersion=$COMMIT
|
||||
|
||||
# Publish stage - optimize for target platform
|
||||
FROM build AS publish
|
||||
ARG TARGETPLATFORM
|
||||
ARG VERSION
|
||||
ARG COMMIT
|
||||
|
||||
RUN dotnet publish "BCards.Web.csproj" \
|
||||
-c Release \
|
||||
-o /app/publish \
|
||||
--no-restore \
|
||||
--no-build \
|
||||
--runtime $(echo $TARGETPLATFORM | tr '/' '-') \
|
||||
--self-contained false \
|
||||
-p:PublishReadyToRun=true \
|
||||
-p:PublishSingleFile=false \
|
||||
-p:UseAppHost=false \
|
||||
-p:Version=$VERSION \
|
||||
-p:InformationalVersion=$COMMIT
|
||||
|
||||
# Final stage - runtime optimized for Release environment
|
||||
FROM base AS final
|
||||
ARG VERSION=unknown
|
||||
ARG COMMIT=unknown
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
# Metadata labels
|
||||
LABEL maintainer="BCards Team"
|
||||
LABEL version=$VERSION
|
||||
LABEL commit=$COMMIT
|
||||
LABEL platform=$TARGETPLATFORM
|
||||
LABEL environment="release"
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy published application
|
||||
COPY --from=publish /app/publish .
|
||||
|
||||
# Create non-root user for security
|
||||
RUN groupadd -r bcards && useradd -r -g bcards bcards \
|
||||
&& chown -R bcards:bcards /app \
|
||||
&& chmod +x /app/BCards.Web.dll
|
||||
|
||||
# Environment variables for Release
|
||||
ENV ASPNETCORE_ENVIRONMENT=Release
|
||||
ENV ASPNETCORE_URLS=http://+:8080
|
||||
ENV DOTNET_RUNNING_IN_CONTAINER=true
|
||||
ENV DOTNET_EnableDiagnostics=0
|
||||
ENV DOTNET_USE_POLLING_FILE_WATCHER=true
|
||||
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false
|
||||
|
||||
# ARM64 specific optimizations
|
||||
RUN if [ "$TARGETPLATFORM" = "linux/arm64" ]; then \
|
||||
echo "Applying ARM64 optimizations..." && \
|
||||
export DOTNET_TieredPGO=1 && \
|
||||
export DOTNET_TC_QuickJitForLoops=1 && \
|
||||
export DOTNET_ReadyToRun=0; \
|
||||
fi
|
||||
|
||||
# Health check endpoint
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
|
||||
CMD curl -f http://localhost:8080/health || exit 1
|
||||
|
||||
# Switch to non-root user
|
||||
USER bcards
|
||||
|
||||
# Entry point with optimized runtime settings
|
||||
ENTRYPOINT ["dotnet", "BCards.Web.dll"]
|
||||
|
||||
# Runtime configuration for better performance
|
||||
CMD ["--urls", "http://0.0.0.0:8080"]
|
||||
160
docker-compose.staging.yml
Normal file
160
docker-compose.staging.yml
Normal file
@ -0,0 +1,160 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
bcards-web:
|
||||
image: ${REGISTRY:-registry.redecarneir.us}/bcards:${IMAGE_TAG:-release-latest}
|
||||
container_name: bcards-staging
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8090:8080"
|
||||
- "8453:8443"
|
||||
environment:
|
||||
# Core ASP.NET Configuration
|
||||
- ASPNETCORE_ENVIRONMENT=Release
|
||||
- ASPNETCORE_URLS=http://+:8080
|
||||
- ASPNETCORE_FORWARDEDHEADERS_ENABLED=true
|
||||
|
||||
# MongoDB Configuration
|
||||
- MongoDb__ConnectionString=${MONGODB_CONNECTION_STRING:-mongodb://192.168.0.100:27017/BCardsDB}
|
||||
- MongoDb__DatabaseName=BCardsDB
|
||||
|
||||
# Application Settings
|
||||
- AppSettings__Environment=Staging
|
||||
- AppSettings__Version=${IMAGE_TAG:-unknown}
|
||||
- AppSettings__AllowedHosts=*
|
||||
|
||||
# Logging Configuration
|
||||
- Logging__LogLevel__Default=Information
|
||||
- Logging__LogLevel__Microsoft.AspNetCore=Warning
|
||||
- Logging__LogLevel__BCards=Debug
|
||||
|
||||
# Performance Optimizations
|
||||
- DOTNET_RUNNING_IN_CONTAINER=true
|
||||
- DOTNET_EnableDiagnostics=0
|
||||
- DOTNET_USE_POLLING_FILE_WATCHER=true
|
||||
- DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false
|
||||
- DOTNET_TieredPGO=1
|
||||
- DOTNET_TC_QuickJitForLoops=1
|
||||
|
||||
# Security Headers
|
||||
- ASPNETCORE_HTTPS_PORT=8443
|
||||
- ASPNETCORE_Kestrel__Certificates__Default__Path=/app/certs/cert.pfx
|
||||
- ASPNETCORE_Kestrel__Certificates__Default__Password=${CERT_PASSWORD:-}
|
||||
|
||||
# Redis Configuration (if needed)
|
||||
- Redis__ConnectionString=localhost:6379
|
||||
|
||||
volumes:
|
||||
# Application logs
|
||||
- ./logs:/app/logs:rw
|
||||
|
||||
# File uploads (if needed)
|
||||
- ./uploads:/app/uploads:rw
|
||||
|
||||
# SSL certificates (if using HTTPS)
|
||||
# - ./certs:/app/certs:ro
|
||||
|
||||
networks:
|
||||
- bcards-staging-network
|
||||
|
||||
# Health check configuration
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
|
||||
# Resource limits for staging environment
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 1G
|
||||
cpus: '1.0'
|
||||
reservations:
|
||||
memory: 512M
|
||||
cpus: '0.5'
|
||||
|
||||
# Logging configuration
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "100m"
|
||||
max-file: "5"
|
||||
|
||||
# Platform specification (will use the appropriate arch from multi-arch image)
|
||||
# platform: linux/amd64 # Uncomment if forcing specific architecture
|
||||
|
||||
# Security options
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
read_only: false # Set to true for extra security, but may need volume mounts for temp files
|
||||
|
||||
# Process limits
|
||||
ulimits:
|
||||
nproc: 65535
|
||||
nofile:
|
||||
soft: 65535
|
||||
hard: 65535
|
||||
|
||||
# Optional: Redis for caching (if application uses it)
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: bcards-redis-staging
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "6379:6379"
|
||||
command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
|
||||
volumes:
|
||||
- redis_staging_data:/data
|
||||
networks:
|
||||
- bcards-staging-network
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 256M
|
||||
cpus: '0.5'
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
|
||||
# Optional: Nginx reverse proxy for additional features
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
container_name: bcards-nginx-staging
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8091:80"
|
||||
- "8454:443"
|
||||
volumes:
|
||||
- ./nginx/staging.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
- ./nginx/ssl:/etc/ssl/certs:ro
|
||||
- ./logs/nginx:/var/log/nginx:rw
|
||||
depends_on:
|
||||
- bcards-web
|
||||
networks:
|
||||
- bcards-staging-network
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 128M
|
||||
cpus: '0.25'
|
||||
|
||||
# Named volumes for persistent data
|
||||
volumes:
|
||||
redis_staging_data:
|
||||
driver: local
|
||||
driver_opts:
|
||||
type: none
|
||||
o: bind
|
||||
device: ./data/redis
|
||||
|
||||
# Network for staging environment
|
||||
networks:
|
||||
bcards-staging-network:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.20.0.0/16
|
||||
369
scripts/deploy-release.sh
Normal file
369
scripts/deploy-release.sh
Normal file
@ -0,0 +1,369 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Deploy script for Release environment with multi-architecture support
|
||||
# Usage: ./deploy-release.sh <IMAGE_TAG>
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Configuration
|
||||
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
readonly PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
readonly DEPLOY_DIR="/opt/bcards-staging"
|
||||
readonly DOCKER_COMPOSE_FILE="docker-compose.staging.yml"
|
||||
readonly CONTAINER_NAME="bcards-staging"
|
||||
readonly HEALTH_CHECK_URL="http://localhost:8090/health"
|
||||
readonly MAX_HEALTH_CHECK_ATTEMPTS=10
|
||||
readonly HEALTH_CHECK_INTERVAL=10
|
||||
readonly ROLLBACK_TIMEOUT=300
|
||||
|
||||
# Colors for output
|
||||
readonly RED='\033[0;31m'
|
||||
readonly GREEN='\033[0;32m'
|
||||
readonly YELLOW='\033[1;33m'
|
||||
readonly BLUE='\033[0;34m'
|
||||
readonly NC='\033[0m' # No Color
|
||||
|
||||
# Logging functions
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Cleanup function
|
||||
cleanup() {
|
||||
local exit_code=$?
|
||||
if [ $exit_code -ne 0 ]; then
|
||||
log_error "Deployment failed with exit code $exit_code"
|
||||
rollback_deployment
|
||||
fi
|
||||
exit $exit_code
|
||||
}
|
||||
|
||||
# Set trap for cleanup on exit
|
||||
trap cleanup EXIT
|
||||
|
||||
# Validate input parameters
|
||||
validate_input() {
|
||||
if [ $# -ne 1 ]; then
|
||||
log_error "Usage: $0 <IMAGE_TAG>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local image_tag="$1"
|
||||
if [[ ! "$image_tag" =~ ^[a-zA-Z0-9._-]+$ ]]; then
|
||||
log_error "Invalid image tag format: $image_tag"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Check prerequisites
|
||||
check_prerequisites() {
|
||||
log_info "Checking prerequisites..."
|
||||
|
||||
# Check if Docker is running
|
||||
if ! docker info >/dev/null 2>&1; then
|
||||
log_error "Docker is not running or not accessible"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if docker-compose is available
|
||||
if ! command -v docker-compose >/dev/null 2>&1; then
|
||||
log_error "docker-compose is not installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if deployment directory exists
|
||||
if [ ! -d "$DEPLOY_DIR" ]; then
|
||||
log_info "Creating deployment directory: $DEPLOY_DIR"
|
||||
mkdir -p "$DEPLOY_DIR"
|
||||
fi
|
||||
|
||||
log_success "Prerequisites check passed"
|
||||
}
|
||||
|
||||
# Backup current deployment
|
||||
backup_current_deployment() {
|
||||
log_info "Backing up current deployment..."
|
||||
|
||||
local timestamp=$(date +%Y%m%d_%H%M%S)
|
||||
local backup_dir="$DEPLOY_DIR/backups/$timestamp"
|
||||
|
||||
mkdir -p "$backup_dir"
|
||||
|
||||
# Backup environment file if exists
|
||||
if [ -f "$DEPLOY_DIR/.env" ]; then
|
||||
cp "$DEPLOY_DIR/.env" "$backup_dir/.env.backup"
|
||||
cp "$DEPLOY_DIR/.env" "$DEPLOY_DIR/.env.backup"
|
||||
log_info "Environment file backed up"
|
||||
fi
|
||||
|
||||
# Backup docker-compose file if exists
|
||||
if [ -f "$DEPLOY_DIR/$DOCKER_COMPOSE_FILE" ]; then
|
||||
cp "$DEPLOY_DIR/$DOCKER_COMPOSE_FILE" "$backup_dir/${DOCKER_COMPOSE_FILE}.backup"
|
||||
log_info "Docker compose file backed up"
|
||||
fi
|
||||
|
||||
# Get current container image for potential rollback
|
||||
if docker ps --format "table {{.Names}}\t{{.Image}}" | grep -q "$CONTAINER_NAME"; then
|
||||
local current_image=$(docker inspect --format='{{.Config.Image}}' "$CONTAINER_NAME" 2>/dev/null || echo "")
|
||||
if [ -n "$current_image" ]; then
|
||||
echo "$current_image" > "$DEPLOY_DIR/.previous_image"
|
||||
log_info "Current image backed up: $current_image"
|
||||
fi
|
||||
fi
|
||||
|
||||
log_success "Backup completed: $backup_dir"
|
||||
}
|
||||
|
||||
# Test MongoDB connectivity
|
||||
test_mongodb_connection() {
|
||||
log_info "Testing MongoDB connectivity..."
|
||||
|
||||
local mongodb_host="192.168.0.100"
|
||||
local mongodb_port="27017"
|
||||
|
||||
# Test basic connectivity
|
||||
if timeout 10 bash -c "</dev/tcp/$mongodb_host/$mongodb_port" 2>/dev/null; then
|
||||
log_success "MongoDB connection test passed"
|
||||
else
|
||||
log_error "Cannot connect to MongoDB at $mongodb_host:$mongodb_port"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Run detailed MongoDB test script if available
|
||||
if [ -f "$SCRIPT_DIR/test-mongodb-connection.sh" ]; then
|
||||
log_info "Running detailed MongoDB connection tests..."
|
||||
bash "$SCRIPT_DIR/test-mongodb-connection.sh"
|
||||
fi
|
||||
}
|
||||
|
||||
# Pull new Docker image
|
||||
pull_docker_image() {
|
||||
local image_tag="$1"
|
||||
local full_image="registry.redecarneir.us/bcards:$image_tag"
|
||||
|
||||
log_info "Pulling Docker image: $full_image"
|
||||
|
||||
# Pull the multi-arch image
|
||||
if docker pull "$full_image"; then
|
||||
log_success "Image pulled successfully"
|
||||
else
|
||||
log_error "Failed to pull image: $full_image"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Verify image architecture
|
||||
local image_arch=$(docker inspect --format='{{.Architecture}}' "$full_image" 2>/dev/null || echo "unknown")
|
||||
local system_arch=$(uname -m)
|
||||
|
||||
log_info "Image architecture: $image_arch"
|
||||
log_info "System architecture: $system_arch"
|
||||
|
||||
# Convert system arch format to Docker format for comparison
|
||||
case "$system_arch" in
|
||||
x86_64) system_arch="amd64" ;;
|
||||
aarch64) system_arch="arm64" ;;
|
||||
esac
|
||||
|
||||
if [ "$image_arch" = "$system_arch" ] || [ "$image_arch" = "unknown" ]; then
|
||||
log_success "Image architecture is compatible"
|
||||
else
|
||||
log_warning "Image architecture ($image_arch) may not match system ($system_arch), but multi-arch support should handle this"
|
||||
fi
|
||||
}
|
||||
|
||||
# Deploy new version
|
||||
deploy_new_version() {
|
||||
local image_tag="$1"
|
||||
|
||||
log_info "Deploying new version with tag: $image_tag"
|
||||
|
||||
# Copy docker-compose file to deployment directory
|
||||
cp "$PROJECT_ROOT/$DOCKER_COMPOSE_FILE" "$DEPLOY_DIR/"
|
||||
|
||||
# Create/update environment file
|
||||
cat > "$DEPLOY_DIR/.env" << EOF
|
||||
IMAGE_TAG=$image_tag
|
||||
REGISTRY=registry.redecarneir.us
|
||||
MONGODB_CONNECTION_STRING=mongodb://192.168.0.100:27017/BCardsDB
|
||||
ASPNETCORE_ENVIRONMENT=Release
|
||||
CERT_PASSWORD=
|
||||
EOF
|
||||
|
||||
# Stop existing containers
|
||||
cd "$DEPLOY_DIR"
|
||||
if docker-compose -f "$DOCKER_COMPOSE_FILE" ps -q | grep -q .; then
|
||||
log_info "Stopping existing containers..."
|
||||
docker-compose -f "$DOCKER_COMPOSE_FILE" down --remove-orphans
|
||||
fi
|
||||
|
||||
# Start new containers
|
||||
log_info "Starting new containers..."
|
||||
docker-compose -f "$DOCKER_COMPOSE_FILE" up -d
|
||||
|
||||
# Wait for containers to start
|
||||
sleep 15
|
||||
|
||||
log_success "New version deployed"
|
||||
}
|
||||
|
||||
# Health check
|
||||
perform_health_check() {
|
||||
log_info "Performing health check..."
|
||||
|
||||
local attempt=1
|
||||
while [ $attempt -le $MAX_HEALTH_CHECK_ATTEMPTS ]; do
|
||||
log_info "Health check attempt $attempt/$MAX_HEALTH_CHECK_ATTEMPTS"
|
||||
|
||||
# Check if container is running
|
||||
if ! docker ps --format "table {{.Names}}" | grep -q "$CONTAINER_NAME"; then
|
||||
log_warning "Container $CONTAINER_NAME is not running"
|
||||
else
|
||||
# Check application health endpoint
|
||||
if curl -f -s "$HEALTH_CHECK_URL" >/dev/null 2>&1; then
|
||||
log_success "Health check passed"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check if the application is responding on port 80
|
||||
if curl -f -s "http://localhost:8090/" >/dev/null 2>&1; then
|
||||
log_success "Application is responding (health endpoint may not be configured)"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $attempt -eq $MAX_HEALTH_CHECK_ATTEMPTS ]; then
|
||||
log_error "Health check failed after $MAX_HEALTH_CHECK_ATTEMPTS attempts"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "Waiting $HEALTH_CHECK_INTERVAL seconds before next attempt..."
|
||||
sleep $HEALTH_CHECK_INTERVAL
|
||||
((attempt++))
|
||||
done
|
||||
}
|
||||
|
||||
# Rollback deployment
|
||||
rollback_deployment() {
|
||||
log_warning "Initiating rollback..."
|
||||
|
||||
cd "$DEPLOY_DIR"
|
||||
|
||||
# Stop current containers
|
||||
if [ -f "$DOCKER_COMPOSE_FILE" ]; then
|
||||
docker-compose -f "$DOCKER_COMPOSE_FILE" down --remove-orphans
|
||||
fi
|
||||
|
||||
# Restore previous environment if backup exists
|
||||
if [ -f ".env.backup" ]; then
|
||||
mv ".env.backup" ".env"
|
||||
log_info "Previous environment restored"
|
||||
fi
|
||||
|
||||
# Try to start previous version if image is available
|
||||
if [ -f ".previous_image" ]; then
|
||||
local previous_image=$(cat ".previous_image")
|
||||
log_info "Attempting to restore previous image: $previous_image"
|
||||
|
||||
# Update .env with previous image tag
|
||||
local previous_tag=$(echo "$previous_image" | cut -d':' -f2)
|
||||
sed -i "s/IMAGE_TAG=.*/IMAGE_TAG=$previous_tag/" .env 2>/dev/null || true
|
||||
|
||||
# Try to start previous version
|
||||
if docker-compose -f "$DOCKER_COMPOSE_FILE" up -d; then
|
||||
log_success "Rollback completed successfully"
|
||||
else
|
||||
log_error "Rollback failed - manual intervention required"
|
||||
fi
|
||||
else
|
||||
log_warning "No previous version found for rollback"
|
||||
fi
|
||||
}
|
||||
|
||||
# Cleanup old images and containers
|
||||
cleanup_old_resources() {
|
||||
log_info "Cleaning up old Docker resources..."
|
||||
|
||||
# Remove dangling images
|
||||
if docker images -f "dangling=true" -q | head -1 | grep -q .; then
|
||||
docker rmi $(docker images -f "dangling=true" -q) || true
|
||||
log_info "Dangling images removed"
|
||||
fi
|
||||
|
||||
# Remove old backups (keep last 5)
|
||||
if [ -d "$DEPLOY_DIR/backups" ]; then
|
||||
find "$DEPLOY_DIR/backups" -maxdepth 1 -type d -name "20*" | sort -r | tail -n +6 | xargs rm -rf || true
|
||||
log_info "Old backups cleaned up"
|
||||
fi
|
||||
|
||||
log_success "Cleanup completed"
|
||||
}
|
||||
|
||||
# Display deployment summary
|
||||
display_summary() {
|
||||
local image_tag="$1"
|
||||
|
||||
log_success "Deployment Summary:"
|
||||
echo "=================================="
|
||||
echo "🚀 Image Tag: $image_tag"
|
||||
echo "🌐 Environment: Release (Staging)"
|
||||
echo "🔗 Application URL: http://localhost:8090"
|
||||
echo "🔗 Health Check: $HEALTH_CHECK_URL"
|
||||
echo "🗄️ MongoDB: 192.168.0.100:27017"
|
||||
echo "📁 Deploy Directory: $DEPLOY_DIR"
|
||||
echo "🐳 Container: $CONTAINER_NAME"
|
||||
|
||||
# Show container status
|
||||
echo ""
|
||||
echo "Container Status:"
|
||||
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep -E "(NAMES|$CONTAINER_NAME)" || true
|
||||
|
||||
# Show image info
|
||||
echo ""
|
||||
echo "Image Information:"
|
||||
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.CreatedAt}}\t{{.Size}}" | grep -E "(REPOSITORY|bcards)" | head -5 || true
|
||||
|
||||
echo "=================================="
|
||||
}
|
||||
|
||||
# Main deployment function
|
||||
main() {
|
||||
local image_tag="$1"
|
||||
|
||||
log_info "Starting deployment process for BCards Release environment"
|
||||
log_info "Target image tag: $image_tag"
|
||||
log_info "Target architecture: $(uname -m)"
|
||||
log_info "Deploy directory: $DEPLOY_DIR"
|
||||
|
||||
# Execute deployment steps
|
||||
validate_input "$@"
|
||||
check_prerequisites
|
||||
test_mongodb_connection
|
||||
backup_current_deployment
|
||||
pull_docker_image "$image_tag"
|
||||
deploy_new_version "$image_tag"
|
||||
|
||||
# Perform health check (rollback handled by trap if this fails)
|
||||
if perform_health_check; then
|
||||
cleanup_old_resources
|
||||
display_summary "$image_tag"
|
||||
log_success "Deployment completed successfully!"
|
||||
else
|
||||
log_error "Health check failed - rollback will be triggered"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Run main function with all arguments
|
||||
main "$@"
|
||||
495
scripts/test-mongodb-connection.sh
Normal file
495
scripts/test-mongodb-connection.sh
Normal file
@ -0,0 +1,495 @@
|
||||
#!/bin/bash
|
||||
|
||||
# MongoDB Connection Test Script for Release Environment
|
||||
# Tests connectivity, database operations, and index validation
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Configuration
|
||||
readonly MONGODB_HOST="${MONGODB_HOST:-192.168.0.100}"
|
||||
readonly MONGODB_PORT="${MONGODB_PORT:-27017}"
|
||||
readonly DATABASE_NAME="${DATABASE_NAME:-BCardsDB}"
|
||||
readonly CONNECTION_STRING="mongodb://${MONGODB_HOST}:${MONGODB_PORT}/${DATABASE_NAME}"
|
||||
readonly TIMEOUT=30
|
||||
|
||||
# Colors for output
|
||||
readonly RED='\033[0;31m'
|
||||
readonly GREEN='\033[0;32m'
|
||||
readonly YELLOW='\033[1;33m'
|
||||
readonly BLUE='\033[0;34m'
|
||||
readonly NC='\033[0m' # No Color
|
||||
|
||||
# Logging functions
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Test basic TCP connectivity
|
||||
test_tcp_connection() {
|
||||
log_info "Testing TCP connection to $MONGODB_HOST:$MONGODB_PORT..."
|
||||
|
||||
if timeout $TIMEOUT bash -c "</dev/tcp/$MONGODB_HOST/$MONGODB_PORT" 2>/dev/null; then
|
||||
log_success "TCP connection successful"
|
||||
return 0
|
||||
else
|
||||
log_error "TCP connection failed"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Test MongoDB connectivity using mongosh if available
|
||||
test_mongodb_with_mongosh() {
|
||||
if ! command -v mongosh >/dev/null 2>&1; then
|
||||
log_warning "mongosh not available, skipping MongoDB shell tests"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "Testing MongoDB connection with mongosh..."
|
||||
|
||||
# Test basic connection
|
||||
local test_output=$(timeout $TIMEOUT mongosh "$CONNECTION_STRING" --quiet --eval "db.runCommand({ping: 1})" 2>/dev/null || echo "FAILED")
|
||||
|
||||
if [[ "$test_output" == *"ok"* ]]; then
|
||||
log_success "MongoDB ping successful"
|
||||
else
|
||||
log_error "MongoDB ping failed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Test database access
|
||||
log_info "Testing database operations..."
|
||||
|
||||
local db_test=$(timeout $TIMEOUT mongosh "$CONNECTION_STRING" --quiet --eval "
|
||||
try {
|
||||
// Test basic database operations
|
||||
db.connection_test.insertOne({test: true, timestamp: new Date()});
|
||||
var result = db.connection_test.findOne({test: true});
|
||||
db.connection_test.deleteOne({test: true});
|
||||
print('DATABASE_ACCESS_OK');
|
||||
} catch (e) {
|
||||
print('DATABASE_ACCESS_FAILED: ' + e.message);
|
||||
}
|
||||
" 2>/dev/null || echo "DATABASE_ACCESS_FAILED")
|
||||
|
||||
if [[ "$db_test" == *"DATABASE_ACCESS_OK"* ]]; then
|
||||
log_success "Database operations test passed"
|
||||
else
|
||||
log_error "Database operations test failed: $db_test"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Test MongoDB connectivity using Python if available
|
||||
test_mongodb_with_python() {
|
||||
if ! command -v python3 >/dev/null 2>&1; then
|
||||
log_warning "Python3 not available, skipping Python MongoDB tests"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "Testing MongoDB connection with Python..."
|
||||
|
||||
python3 << EOF
|
||||
import sys
|
||||
try:
|
||||
import pymongo
|
||||
from pymongo import MongoClient
|
||||
import socket
|
||||
|
||||
# Test connection
|
||||
client = MongoClient("$CONNECTION_STRING", serverSelectionTimeoutMS=$((TIMEOUT * 1000)))
|
||||
|
||||
# Test ping
|
||||
client.admin.command('ping')
|
||||
print("MongoDB ping successful (Python)")
|
||||
|
||||
# Test database access
|
||||
db = client["$DATABASE_NAME"]
|
||||
|
||||
# Insert test document
|
||||
test_collection = db.connection_test
|
||||
result = test_collection.insert_one({"test": True, "source": "python"})
|
||||
|
||||
# Read test document
|
||||
doc = test_collection.find_one({"_id": result.inserted_id})
|
||||
if doc:
|
||||
print("Database read/write test passed (Python)")
|
||||
|
||||
# Cleanup
|
||||
test_collection.delete_one({"_id": result.inserted_id})
|
||||
|
||||
client.close()
|
||||
print("PYTHON_TEST_SUCCESS")
|
||||
|
||||
except ImportError:
|
||||
print("PyMongo not installed, skipping Python tests")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"Python MongoDB test failed: {e}")
|
||||
sys.exit(1)
|
||||
EOF
|
||||
|
||||
local python_result=$?
|
||||
if [ $python_result -eq 0 ]; then
|
||||
log_success "Python MongoDB test passed"
|
||||
return 0
|
||||
else
|
||||
log_error "Python MongoDB test failed"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Test using Docker MongoDB client
|
||||
test_mongodb_with_docker() {
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
log_warning "Docker not available, skipping Docker MongoDB tests"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "Testing MongoDB connection using Docker MongoDB client..."
|
||||
|
||||
# Use official MongoDB image to test connection
|
||||
local docker_test=$(timeout $TIMEOUT docker run --rm mongo:7.0 mongosh "$CONNECTION_STRING" --quiet --eval "
|
||||
try {
|
||||
db.runCommand({ping: 1});
|
||||
db.connection_test.insertOne({test: true, source: 'docker', timestamp: new Date()});
|
||||
var doc = db.connection_test.findOne({source: 'docker'});
|
||||
db.connection_test.deleteOne({source: 'docker'});
|
||||
print('DOCKER_TEST_SUCCESS');
|
||||
} catch (e) {
|
||||
print('DOCKER_TEST_FAILED: ' + e.message);
|
||||
}
|
||||
" 2>/dev/null || echo "DOCKER_TEST_FAILED")
|
||||
|
||||
if [[ "$docker_test" == *"DOCKER_TEST_SUCCESS"* ]]; then
|
||||
log_success "Docker MongoDB test passed"
|
||||
return 0
|
||||
else
|
||||
log_error "Docker MongoDB test failed: $docker_test"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Test MongoDB from application container
|
||||
test_from_application_container() {
|
||||
local container_name="bcards-staging"
|
||||
|
||||
if ! docker ps --format "{{.Names}}" | grep -q "^${container_name}$"; then
|
||||
log_warning "Application container '$container_name' not running, skipping application test"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "Testing MongoDB connection from application container..."
|
||||
|
||||
# Test connection from the application container
|
||||
local app_test=$(docker exec "$container_name" timeout 10 bash -c "
|
||||
# Test TCP connection
|
||||
if timeout 5 bash -c '</dev/tcp/$MONGODB_HOST/$MONGODB_PORT' 2>/dev/null; then
|
||||
echo 'APP_TCP_OK'
|
||||
else
|
||||
echo 'APP_TCP_FAILED'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test HTTP health endpoint if available
|
||||
if curl -f -s http://localhost:8080/health >/dev/null 2>&1; then
|
||||
echo 'APP_HEALTH_OK'
|
||||
else
|
||||
echo 'APP_HEALTH_FAILED'
|
||||
fi
|
||||
" 2>/dev/null || echo "APP_TEST_FAILED")
|
||||
|
||||
if [[ "$app_test" == *"APP_TCP_OK"* ]]; then
|
||||
log_success "Application container can connect to MongoDB"
|
||||
|
||||
if [[ "$app_test" == *"APP_HEALTH_OK"* ]]; then
|
||||
log_success "Application health check passed"
|
||||
else
|
||||
log_warning "Application health check failed - app may still be starting"
|
||||
fi
|
||||
return 0
|
||||
else
|
||||
log_error "Application container cannot connect to MongoDB"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Check MongoDB server status and version
|
||||
check_mongodb_status() {
|
||||
log_info "Checking MongoDB server status..."
|
||||
|
||||
# Try multiple methods to check status
|
||||
local status_checked=false
|
||||
|
||||
# Method 1: Using mongosh
|
||||
if command -v mongosh >/dev/null 2>&1; then
|
||||
local server_status=$(timeout $TIMEOUT mongosh "$CONNECTION_STRING" --quiet --eval "
|
||||
try {
|
||||
var status = db.runCommand({serverStatus: 1});
|
||||
print('MongoDB Version: ' + status.version);
|
||||
print('Uptime: ' + status.uptime + ' seconds');
|
||||
print('Connections: ' + status.connections.current + '/' + status.connections.available);
|
||||
print('STATUS_CHECK_OK');
|
||||
} catch (e) {
|
||||
print('STATUS_CHECK_FAILED: ' + e.message);
|
||||
}
|
||||
" 2>/dev/null || echo "STATUS_CHECK_FAILED")
|
||||
|
||||
if [[ "$server_status" == *"STATUS_CHECK_OK"* ]]; then
|
||||
echo "$server_status" | grep -v "STATUS_CHECK_OK"
|
||||
log_success "MongoDB server status check passed"
|
||||
status_checked=true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Method 2: Using Docker if mongosh failed
|
||||
if [ "$status_checked" = false ] && command -v docker >/dev/null 2>&1; then
|
||||
local docker_status=$(timeout $TIMEOUT docker run --rm mongo:7.0 mongosh "$CONNECTION_STRING" --quiet --eval "
|
||||
try {
|
||||
var status = db.runCommand({serverStatus: 1});
|
||||
print('MongoDB Version: ' + status.version);
|
||||
print('STATUS_CHECK_OK');
|
||||
} catch (e) {
|
||||
print('STATUS_CHECK_FAILED: ' + e.message);
|
||||
}
|
||||
" 2>/dev/null || echo "STATUS_CHECK_FAILED")
|
||||
|
||||
if [[ "$docker_status" == *"STATUS_CHECK_OK"* ]]; then
|
||||
echo "$docker_status" | grep -v "STATUS_CHECK_OK"
|
||||
log_success "MongoDB server status check passed (via Docker)"
|
||||
status_checked=true
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$status_checked" = false ]; then
|
||||
log_warning "Could not retrieve MongoDB server status"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Test BCards specific collections and indexes
|
||||
test_bcards_collections() {
|
||||
if ! command -v mongosh >/dev/null 2>&1 && ! command -v docker >/dev/null 2>&1; then
|
||||
log_warning "Cannot test BCards collections - no MongoDB client available"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "Testing BCards specific collections and indexes..."
|
||||
|
||||
local mongo_cmd="mongosh"
|
||||
local docker_prefix=""
|
||||
|
||||
if ! command -v mongosh >/dev/null 2>&1; then
|
||||
mongo_cmd="docker run --rm mongo:7.0 mongosh"
|
||||
docker_prefix="timeout $TIMEOUT "
|
||||
fi
|
||||
|
||||
local collections_test=$(${docker_prefix}${mongo_cmd} "$CONNECTION_STRING" --quiet --eval "
|
||||
try {
|
||||
// Check required collections
|
||||
var collections = db.listCollectionNames();
|
||||
var requiredCollections = ['users', 'userpages', 'categories'];
|
||||
var missingCollections = [];
|
||||
|
||||
requiredCollections.forEach(function(collection) {
|
||||
if (collections.indexOf(collection) === -1) {
|
||||
missingCollections.push(collection);
|
||||
}
|
||||
});
|
||||
|
||||
if (missingCollections.length > 0) {
|
||||
print('Missing collections: ' + missingCollections.join(', '));
|
||||
} else {
|
||||
print('All required collections exist');
|
||||
}
|
||||
|
||||
// Check indexes on userpages collection
|
||||
if (collections.indexOf('userpages') !== -1) {
|
||||
var indexes = db.userpages.getIndexes();
|
||||
print('UserPages collection has ' + indexes.length + ' indexes');
|
||||
|
||||
// Check for important compound index
|
||||
var hasCompoundIndex = indexes.some(function(index) {
|
||||
return index.key && index.key.category && index.key.slug;
|
||||
});
|
||||
|
||||
if (hasCompoundIndex) {
|
||||
print('Required compound index (category, slug) exists');
|
||||
} else {
|
||||
print('WARNING: Compound index (category, slug) is missing');
|
||||
}
|
||||
}
|
||||
|
||||
print('COLLECTIONS_TEST_OK');
|
||||
} catch (e) {
|
||||
print('COLLECTIONS_TEST_FAILED: ' + e.message);
|
||||
}
|
||||
" 2>/dev/null || echo "COLLECTIONS_TEST_FAILED")
|
||||
|
||||
if [[ "$collections_test" == *"COLLECTIONS_TEST_OK"* ]]; then
|
||||
echo "$collections_test" | grep -v "COLLECTIONS_TEST_OK"
|
||||
log_success "BCards collections test passed"
|
||||
return 0
|
||||
else
|
||||
log_warning "BCards collections test had issues: $collections_test"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Performance test
|
||||
test_mongodb_performance() {
|
||||
log_info "Running basic performance test..."
|
||||
|
||||
if ! command -v mongosh >/dev/null 2>&1; then
|
||||
log_warning "mongosh not available, skipping performance test"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local perf_test=$(timeout $TIMEOUT mongosh "$CONNECTION_STRING" --quiet --eval "
|
||||
try {
|
||||
var start = new Date();
|
||||
|
||||
// Insert test documents
|
||||
var docs = [];
|
||||
for (var i = 0; i < 100; i++) {
|
||||
docs.push({test: true, index: i, timestamp: new Date()});
|
||||
}
|
||||
db.performance_test.insertMany(docs);
|
||||
|
||||
// Read test
|
||||
var count = db.performance_test.countDocuments({test: true});
|
||||
|
||||
// Update test
|
||||
db.performance_test.updateMany({test: true}, {\$set: {updated: true}});
|
||||
|
||||
// Delete test
|
||||
db.performance_test.deleteMany({test: true});
|
||||
|
||||
var end = new Date();
|
||||
var duration = end - start;
|
||||
|
||||
print('Performance test completed in ' + duration + 'ms');
|
||||
print('Operations: 100 inserts, 1 count, 100 updates, 100 deletes');
|
||||
|
||||
if (duration < 5000) {
|
||||
print('PERFORMANCE_TEST_OK');
|
||||
} else {
|
||||
print('PERFORMANCE_TEST_SLOW');
|
||||
}
|
||||
} catch (e) {
|
||||
print('PERFORMANCE_TEST_FAILED: ' + e.message);
|
||||
}
|
||||
" 2>/dev/null || echo "PERFORMANCE_TEST_FAILED")
|
||||
|
||||
if [[ "$perf_test" == *"PERFORMANCE_TEST_OK"* ]]; then
|
||||
echo "$perf_test" | grep -v "PERFORMANCE_TEST_OK"
|
||||
log_success "Performance test passed"
|
||||
return 0
|
||||
elif [[ "$perf_test" == *"PERFORMANCE_TEST_SLOW"* ]]; then
|
||||
echo "$perf_test" | grep -v "PERFORMANCE_TEST_SLOW"
|
||||
log_warning "Performance test completed but was slow"
|
||||
return 0
|
||||
else
|
||||
log_error "Performance test failed: $perf_test"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Display connection summary
|
||||
display_summary() {
|
||||
echo ""
|
||||
log_info "MongoDB Connection Test Summary"
|
||||
echo "=================================================="
|
||||
echo "🏠 Host: $MONGODB_HOST:$MONGODB_PORT"
|
||||
echo "🗄️ Database: $DATABASE_NAME"
|
||||
echo "🔗 Connection String: $CONNECTION_STRING"
|
||||
echo "⏱️ Timeout: ${TIMEOUT}s"
|
||||
echo "📊 Tests completed: $(date)"
|
||||
echo "=================================================="
|
||||
}
|
||||
|
||||
# Main test function
|
||||
main() {
|
||||
log_info "Starting MongoDB connection tests for Release environment"
|
||||
|
||||
local test_results=()
|
||||
local overall_success=true
|
||||
|
||||
# Run all tests
|
||||
if test_tcp_connection; then
|
||||
test_results+=("✅ TCP Connection")
|
||||
else
|
||||
test_results+=("❌ TCP Connection")
|
||||
overall_success=false
|
||||
fi
|
||||
|
||||
if test_mongodb_with_mongosh; then
|
||||
test_results+=("✅ MongoDB Shell")
|
||||
elif test_mongodb_with_docker; then
|
||||
test_results+=("✅ MongoDB Docker")
|
||||
elif test_mongodb_with_python; then
|
||||
test_results+=("✅ MongoDB Python")
|
||||
else
|
||||
test_results+=("❌ MongoDB Client")
|
||||
overall_success=false
|
||||
fi
|
||||
|
||||
if test_from_application_container; then
|
||||
test_results+=("✅ Application Container")
|
||||
else
|
||||
test_results+=("⚠️ Application Container")
|
||||
fi
|
||||
|
||||
if check_mongodb_status; then
|
||||
test_results+=("✅ Server Status")
|
||||
else
|
||||
test_results+=("⚠️ Server Status")
|
||||
fi
|
||||
|
||||
if test_bcards_collections; then
|
||||
test_results+=("✅ BCards Collections")
|
||||
else
|
||||
test_results+=("⚠️ BCards Collections")
|
||||
fi
|
||||
|
||||
if test_mongodb_performance; then
|
||||
test_results+=("✅ Performance Test")
|
||||
else
|
||||
test_results+=("⚠️ Performance Test")
|
||||
fi
|
||||
|
||||
# Display results
|
||||
display_summary
|
||||
echo ""
|
||||
log_info "Test Results:"
|
||||
for result in "${test_results[@]}"; do
|
||||
echo " $result"
|
||||
done
|
||||
|
||||
echo ""
|
||||
if [ "$overall_success" = true ]; then
|
||||
log_success "All critical MongoDB tests passed!"
|
||||
exit 0
|
||||
else
|
||||
log_error "Some critical MongoDB tests failed!"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Run main function
|
||||
main "$@"
|
||||
141
src/BCards.Web/appsettings.Release.json
Normal file
141
src/BCards.Web/appsettings.Release.json
Normal file
@ -0,0 +1,141 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning",
|
||||
"Microsoft.EntityFrameworkCore": "Warning",
|
||||
"BCards": "Information"
|
||||
},
|
||||
"Console": {
|
||||
"IncludeScopes": false,
|
||||
"LogLevel": {
|
||||
"Default": "Information"
|
||||
}
|
||||
},
|
||||
"File": {
|
||||
"Path": "/app/logs/bcards-{Date}.log",
|
||||
"LogLevel": {
|
||||
"Default": "Information"
|
||||
}
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"MongoDb": {
|
||||
"ConnectionString": "mongodb://192.168.0.100:27017/BCardsDB",
|
||||
"DatabaseName": "BCardsDB",
|
||||
"MaxConnectionPoolSize": 100,
|
||||
"ConnectTimeout": "30s",
|
||||
"ServerSelectionTimeout": "30s",
|
||||
"SocketTimeout": "30s"
|
||||
},
|
||||
"Stripe": {
|
||||
"PublishableKey": "pk_test_51RjUmIBMIadsOxJVP4bWc54pHEOSf5km1hpOkOBSoGVoKxI46N4KSWtevpXCSq68OjFazBuXmPJGBwZ1KDN5MNJy003lj1YmAS",
|
||||
"SecretKey": "sk_test_51RjUmIBMIadsOxJVeqsMFxnZ8ePR7d8IbnaF4sAwBVJv9rrfODPEQ2C9fF3beoABpITdfzEk0ZDzGTTQfvKv63xI00PeZoABGO",
|
||||
"WebhookSecret": "whsec_8d189c137ff170ab5e62498003512b9d073e2db50c50ed7d8712b7ef11a37543",
|
||||
"ApiVersion": "2023-10-16"
|
||||
},
|
||||
"Authentication": {
|
||||
"Google": {
|
||||
"ClientId": "472850008574-nmeepbdt4hunsk5c8krpbdmd3olc4jv6.apps.googleusercontent.com",
|
||||
"ClientSecret": "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"
|
||||
},
|
||||
"Microsoft": {
|
||||
"ClientId": "b411606a-e574-4f59-b7cd-10dd941b9fa3",
|
||||
"ClientSecret": "T0.8Q~an.51iW1H0DVjL2i1bmSK_qTgVQOuEmapK"
|
||||
},
|
||||
"CookieSettings": {
|
||||
"SecurePolicy": "Always",
|
||||
"SameSiteMode": "Lax",
|
||||
"HttpOnly": true,
|
||||
"ExpireTimeSpan": "7.00:00:00"
|
||||
}
|
||||
},
|
||||
"Plans": {
|
||||
"Basic": {
|
||||
"PriceId": "price_1RjUskBMIadsOxJVgLwlVo1y",
|
||||
"Price": 9.90,
|
||||
"MaxLinks": 5,
|
||||
"Features": [ "basic_themes", "simple_analytics" ]
|
||||
},
|
||||
"Professional": {
|
||||
"PriceId": "price_1RjUv9BMIadsOxJVORqlM4E9",
|
||||
"Price": 24.90,
|
||||
"MaxLinks": 15,
|
||||
"Features": [ "all_themes", "advanced_analytics", "custom_domain" ]
|
||||
},
|
||||
"Premium": {
|
||||
"PriceId": "price_1RjUw0BMIadsOxJVmdouNV1g",
|
||||
"Price": 29.90,
|
||||
"MaxLinks": -1,
|
||||
"Features": [ "custom_themes", "full_analytics", "multiple_domains", "priority_support" ]
|
||||
}
|
||||
},
|
||||
"Moderation": {
|
||||
"PriorityTimeframes": {
|
||||
"Trial": "7.00:00:00",
|
||||
"Basic": "7.00:00:00",
|
||||
"Professional": "3.00:00:00",
|
||||
"Premium": "1.00:00:00"
|
||||
},
|
||||
"MaxAttempts": 3,
|
||||
"ModeratorEmail": "ricardo.carneiro@jobmaker.com.br",
|
||||
"ModeratorEmails": [
|
||||
"rrcgoncalves@gmail.com",
|
||||
"rirocarneiro@gmail.com"
|
||||
],
|
||||
"PreviewTokenExpirationHours": 4,
|
||||
"AutoRejectDays": 30
|
||||
},
|
||||
"SendGrid": {
|
||||
"ApiKey": "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",
|
||||
"FromEmail": "ricardo.carneiro@jobmaker.com.br",
|
||||
"FromName": "BCards - Staging",
|
||||
"ReplyToEmail": "ricardo.carneiro@jobmaker.com.br"
|
||||
},
|
||||
"BaseUrl": "http://192.168.0.100:8090",
|
||||
"Environment": {
|
||||
"Name": "Release",
|
||||
"IsStagingEnvironment": true,
|
||||
"AllowTestData": true,
|
||||
"EnableDetailedErrors": false
|
||||
},
|
||||
"Performance": {
|
||||
"EnableCaching": true,
|
||||
"CacheExpirationMinutes": 30,
|
||||
"EnableCompression": true,
|
||||
"EnableResponseCaching": true
|
||||
},
|
||||
"Security": {
|
||||
"EnableHttpsRedirection": false,
|
||||
"EnableHsts": false,
|
||||
"RequireHttpsMetadata": false,
|
||||
"CorsOrigins": [
|
||||
"http://192.168.0.100:8090",
|
||||
"http://localhost:8090"
|
||||
]
|
||||
},
|
||||
"HealthChecks": {
|
||||
"Enabled": true,
|
||||
"Endpoints": {
|
||||
"Health": "/health",
|
||||
"Ready": "/ready",
|
||||
"Live": "/live"
|
||||
},
|
||||
"MongoDb": {
|
||||
"Enabled": true,
|
||||
"Timeout": "10s"
|
||||
}
|
||||
},
|
||||
"Monitoring": {
|
||||
"EnableMetrics": true,
|
||||
"MetricsEndpoint": "/metrics",
|
||||
"EnableTracing": false
|
||||
},
|
||||
"Features": {
|
||||
"EnablePreviewMode": true,
|
||||
"EnableModerationWorkflow": true,
|
||||
"EnableAnalytics": true,
|
||||
"EnableFileUploads": true,
|
||||
"MaxFileUploadSize": "5MB"
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user