name: Build, Push and Deploy Florale Emotion Website on: push: branches: [ 'feature/*', 'main', 'master' ] pull_request: branches: [ main, master ] env: HARBOR_REGISTRY: registry.julianvollmer.de PROJECT_NAME: florale-emotion jobs: feature-branch: runs-on: ubuntu-latest timeout-minutes: 60 if: startsWith(github.ref, 'refs/heads/feature/') steps: - name: Checkout code uses: actions/checkout@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 with: driver-opts: | image=moby/buildkit:v0.12.0 buildkitd-flags: --debug - name: Extract metadata id: meta run: | BRANCH_CLEAN=$(echo "${{ github.ref_name }}" | sed 's/[^a-zA-Z0-9._-]/-/g') SHORT_SHA="${{ github.sha }}" SHORT_SHA="${SHORT_SHA:0:8}" TAG="${BRANCH_CLEAN}-${SHORT_SHA}" echo "tag=${TAG}" >> $GITHUB_OUTPUT echo "frontend_image=${{ env.HARBOR_REGISTRY }}/${{ env.PROJECT_NAME }}/florale-emotion-frontend:${TAG}" >> $GITHUB_OUTPUT echo "backend_image=${{ env.HARBOR_REGISTRY }}/${{ env.PROJECT_NAME }}/florale-emotion-backend:${TAG}" >> $GITHUB_OUTPUT echo "bot_image=${{ env.HARBOR_REGISTRY }}/${{ env.PROJECT_NAME }}/florale-emotion-bot:${TAG}" >> $GITHUB_OUTPUT - name: Login to Harbor Registry run: | echo "Harbor12345" | docker login ${{ env.HARBOR_REGISTRY }} -u admin --password-stdin - name: Build and Push Frontend (Feature) working-directory: ./website run: | IMAGE_NAME=${{ steps.meta.outputs.frontend_image }} for attempt in 1 2 3; do echo "Build attempt $attempt for feature frontend..." docker build --no-cache --progress=plain -t "${IMAGE_NAME}" . || { echo "Build failed on attempt $attempt" if [ $attempt -eq 3 ]; then echo "All build attempts failed" exit 1 fi sleep 10 continue } for push_attempt in 1 2 3; do echo "Push attempt $push_attempt for feature frontend..." docker push "${IMAGE_NAME}" && { echo "Feature frontend push successful on attempt $push_attempt" break } || { echo "Push failed on attempt $push_attempt" if [ $push_attempt -eq 3 ]; then echo "All push attempts failed" exit 1 fi sleep 30 } done break done echo "βœ… Feature frontend build and push completed successfully!" echo "🏷️ Tag: ${TAG}" echo "πŸ“¦ Image: ${IMAGE_NAME}" - name: Build and Push Backend (Feature) working-directory: ./website/backend env: IMAGE_NAME: ${{ steps.meta.outputs.backend_image }} TAG: ${{ steps.meta.outputs.tag }} run: | echo "Building backend image: ${IMAGE_NAME}" docker build --no-cache --progress=plain -t "${IMAGE_NAME}" . echo "Pushing backend image: ${IMAGE_NAME}" docker push "${IMAGE_NAME}" echo "βœ… Feature backend build and push completed successfully!" echo "🏷️ Tag: ${TAG}" echo "πŸ“¦ Image: ${IMAGE_NAME}" - name: Build and Push Social Media Bot (Feature) working-directory: ./website/social-media-bot env: IMAGE_NAME: ${{ steps.meta.outputs.bot_image }} TAG: ${{ steps.meta.outputs.tag }} run: | echo "Building social media bot image: ${IMAGE_NAME}" docker build --no-cache --progress=plain -t "${IMAGE_NAME}" . echo "Pushing social media bot image: ${IMAGE_NAME}" docker push "${IMAGE_NAME}" echo "βœ… Feature social media bot build and push completed successfully!" echo "🏷️ Tag: ${TAG}" echo "πŸ“¦ Image: ${IMAGE_NAME}" - name: Setup kubectl run: | # Install kubectl directly to avoid permission issues echo "Installing kubectl..." curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" chmod +x kubectl sudo mv kubectl /usr/local/bin/ kubectl version --client - name: Configure kubectl env: KUBECTLSECRET: ${{ secrets.KUBECTLSECRET }} run: | # Always use an explicit kubeconfig path (do not rely on HOME expansion) export KUBECONFIG="${GITHUB_WORKSPACE}/kubeconfig" echo "πŸ” Debugging KUBECTLSECRET..." echo "Secret length: ${#KUBECTLSECRET}" # Check if secret is empty if [ "${#KUBECTLSECRET}" -eq 0 ]; then echo "❌ ERROR: KUBECTLSECRET is empty!" exit 1 fi # Try to decode as base64 first, if that fails, use as plain text if echo "$KUBECTLSECRET" | base64 -d > "$KUBECONFIG" 2>/dev/null; then echo "βœ… KUBECTLSECRET decoded as base64" else echo "⚠️ KUBECTLSECRET is not base64, using as plain text" echo "$KUBECTLSECRET" > "$KUBECONFIG" fi echo "πŸ“ kubeconfig created at $KUBECONFIG" chmod 600 "$KUBECONFIG" # Safe debug (do NOT print kubeconfig contents) echo "πŸ” kubeconfig sanity checks (safe):" echo "- contains clusters: $(grep -c '^clusters:' "$KUBECONFIG" || echo 0)" echo "- contains contexts: $(grep -c '^contexts:' "$KUBECONFIG" || echo 0)" echo "- contains users: $(grep -c '^users:' "$KUBECONFIG" || echo 0)" echo "- contains current-context: $(grep -c '^current-context:' "$KUBECONFIG" || echo 0)" echo "- contains token: $(grep -c '^[[:space:]]*token:' "$KUBECONFIG" || echo 0)" echo "- contains client-certificate-data: $(grep -c 'client-certificate-data:' "$KUBECONFIG" || echo 0)" echo "- contains client-key-data: $(grep -c 'client-key-data:' "$KUBECONFIG" || echo 0)" echo "- current-context line: $(grep '^current-context:' "$KUBECONFIG" || echo 'NOT FOUND')" - name: Debug kubeconfig before kubectl test env: KUBECONFIG: ${{ github.workspace }}/kubeconfig run: | echo "πŸ” Final kubeconfig debug before kubectl test..." echo "KUBECONFIG: $KUBECONFIG" echo "File exists: $(test -f "$KUBECONFIG" && echo 'YES' || echo 'NO')" echo "File size: $(wc -c < "$KUBECONFIG" 2>/dev/null || echo '0') bytes" if [ -f "$KUBECONFIG" ]; then echo "Contains 'token': $(grep -c '^[[:space:]]*token:' "$KUBECONFIG" || echo '0')" echo "Contains 'client-certificate-data': $(grep -c 'client-certificate-data:' "$KUBECONFIG" || echo '0')" echo "Contains 'client-key-data': $(grep -c 'client-key-data:' "$KUBECONFIG" || echo '0')" echo "Current context: $(grep '^current-context:' "$KUBECONFIG" || echo 'NOT FOUND')" else echo "❌ kubeconfig file does not exist!" fi - name: Test kubectl connection env: KUBECONFIG: ${{ github.workspace }}/kubeconfig run: | kubectl version --client echo "Testing cluster connection..." kubectl get nodes - name: Deploy Feature Branch env: FRONTEND_IMAGE: ${{ steps.meta.outputs.frontend_image }} BACKEND_IMAGE: ${{ steps.meta.outputs.backend_image }} BOT_IMAGE: ${{ steps.meta.outputs.bot_image }} BRANCH_NAME: ${{ github.ref_name }} run: | echo "πŸš€ Deploying feature branch: $BRANCH_NAME" echo "Frontend image: $FRONTEND_IMAGE" echo "Backend image: $BACKEND_IMAGE" echo "Bot image: $BOT_IMAGE" # Ensure namespace exists kubectl create namespace florale-emotion --dry-run=client -o yaml | kubectl apply -f - # Ensure Harbor registry secret exists kubectl create secret docker-registry harbor-registry-secret \ --docker-server=${{ env.HARBOR_REGISTRY }} \ --docker-username=admin \ --docker-password=Harbor12345 \ --namespace=florale-emotion \ --dry-run=client -o yaml | kubectl apply -f - # Apply frontend deployment und ersetze das Image-Tag dynamisch (kein latest in Features) sed "s|__IMAGE_TAG__|${{ steps.meta.outputs.tag }}|g" website/k8s/frontend.yaml | kubectl apply -n florale-emotion -f - # Apply backend deployment mit Tag-Ersetzung sed "s|__IMAGE_TAG__|${{ steps.meta.outputs.tag }}|g" website/k8s/backend.yaml | kubectl apply -n florale-emotion -f - # Apply social media bot deployment mit Tag-Ersetzung sed "s|__IMAGE_TAG__|${{ steps.meta.outputs.tag }}|g" website/k8s/bot.yaml | kubectl apply -n florale-emotion -f - # Apply feature branch ingress (using dev subdomain) kubectl apply -f website/k8s/ingress-dev.yaml -n florale-emotion # Wait for rollout echo "⏳ Waiting for rollout to finish..." kubectl rollout status deployment/florale-emotion-frontend -n florale-emotion --timeout=300s echo "βœ… Feature branch deployment complete!" echo "🌐 Dev URL: https://dev.florale-emotion.de" echo "πŸ“¦ Harbor Registry: https://registry.julianvollmer.de" production-branch: runs-on: ubuntu-latest timeout-minutes: 60 if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' steps: - name: Checkout code uses: actions/checkout@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 with: driver-opts: | image=moby/buildkit:v0.12.0 buildkitd-flags: --debug - name: Extract metadata id: meta run: | BRANCH_CLEAN=$(echo "${{ github.ref_name }}" | sed 's/[^a-zA-Z0-9._-]/-/g') SHORT_SHA="${{ github.sha }}" SHORT_SHA="${SHORT_SHA:0:8}" TAG="${BRANCH_CLEAN}-${SHORT_SHA}" # FΓΌr Master-Branches zusΓ€tzlich latest Tag erstellen echo "Master branch detected, will create latest tags" echo "tag=${TAG}" >> $GITHUB_OUTPUT echo "frontend_image=${{ env.HARBOR_REGISTRY }}/${{ env.PROJECT_NAME }}/florale-emotion-frontend:${TAG}" >> $GITHUB_OUTPUT echo "backend_image=${{ env.HARBOR_REGISTRY }}/${{ env.PROJECT_NAME }}/florale-emotion-backend:${TAG}" >> $GITHUB_OUTPUT echo "bot_image=${{ env.HARBOR_REGISTRY }}/${{ env.PROJECT_NAME }}/florale-emotion-bot:${TAG}" >> $GITHUB_OUTPUT - name: Login to Harbor Registry run: | echo "Harbor12345" | docker login ${{ env.HARBOR_REGISTRY }} -u admin --password-stdin - name: Build and Push Frontend (Production + Latest) working-directory: ./website run: | IMAGE_NAME=${{ steps.meta.outputs.frontend_image }} LATEST_IMAGE="${{ env.HARBOR_REGISTRY }}/${{ env.PROJECT_NAME }}/florale-emotion-frontend:latest" for attempt in 1 2 3; do echo "Build attempt $attempt for production frontend..." docker build --no-cache --progress=plain -t "${IMAGE_NAME}" . || { echo "Build failed on attempt $attempt" if [ $attempt -eq 3 ]; then echo "All build attempts failed" exit 1 fi sleep 10 continue } # Push versioned image for push_attempt in 1 2 3; do echo "Push attempt $push_attempt for production frontend..." docker push "${IMAGE_NAME}" && { echo "Production frontend push successful on attempt $push_attempt" break } || { echo "Push failed on attempt $push_attempt" if [ $push_attempt -eq 3 ]; then echo "All push attempts failed" exit 1 fi sleep 30 } done # Create and push latest tag echo "Creating latest tag for production..." docker tag "${IMAGE_NAME}" "${LATEST_IMAGE}" for push_attempt in 1 2 3; do echo "Push attempt $push_attempt for latest tag..." docker push "${LATEST_IMAGE}" && { echo "Latest tag push successful on attempt $push_attempt" break } || { echo "Latest push failed on attempt $push_attempt" if [ $push_attempt -eq 3 ]; then echo "Latest push attempts failed" exit 1 fi sleep 30 } done break done echo "βœ… Production frontend build and push completed successfully!" echo "🏷️ Tag: ${TAG}" echo "πŸ“¦ Image: ${IMAGE_NAME}" echo "πŸ“¦ Latest: ${LATEST_IMAGE}" - name: Build and Push Backend (Production + Latest) working-directory: ./website/backend env: IMAGE_NAME: ${{ steps.meta.outputs.backend_image }} TAG: ${{ steps.meta.outputs.tag }} run: | echo "Building backend image: ${IMAGE_NAME}" docker build --no-cache --progress=plain -t "${IMAGE_NAME}" . echo "Pushing backend image: ${IMAGE_NAME}" docker push "${IMAGE_NAME}" LATEST_IMAGE="${IMAGE_NAME%:*}:latest" echo "Tagging backend as latest: ${LATEST_IMAGE}" docker tag "${IMAGE_NAME}" "${LATEST_IMAGE}" docker push "${LATEST_IMAGE}" echo "βœ… Production backend build and push completed successfully!" echo "🏷️ Tag: ${TAG}" echo "πŸ“¦ Image: ${IMAGE_NAME}" echo "πŸ“¦ Latest: ${LATEST_IMAGE}" - name: Build and Push Social Media Bot (Production + Latest) working-directory: ./website/social-media-bot env: IMAGE_NAME: ${{ steps.meta.outputs.bot_image }} TAG: ${{ steps.meta.outputs.tag }} run: | echo "Building social media bot image: ${IMAGE_NAME}" docker build --no-cache --progress=plain -t "${IMAGE_NAME}" . echo "Pushing social media bot image: ${IMAGE_NAME}" docker push "${IMAGE_NAME}" LATEST_IMAGE="${IMAGE_NAME%:*}:latest" echo "Tagging social media bot as latest: ${LATEST_IMAGE}" docker tag "${IMAGE_NAME}" "${LATEST_IMAGE}" docker push "${LATEST_IMAGE}" echo "βœ… Production social media bot build and push completed successfully!" echo "🏷️ Tag: ${TAG}" echo "πŸ“¦ Image: ${IMAGE_NAME}" echo "πŸ“¦ Latest: ${LATEST_IMAGE}" - name: Setup kubectl run: | # Install kubectl directly to avoid permission issues echo "Installing kubectl..." curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" chmod +x kubectl sudo mv kubectl /usr/local/bin/ kubectl version --client - name: Configure kubectl env: KUBECTLSECRET: ${{ secrets.KUBECTLSECRET }} run: | mkdir -p ~/.kube echo "πŸ” Debugging KUBECTLSECRET..." echo "Secret length: ${#KUBECTLSECRET}" # Check if secret is empty if [ "${#KUBECTLSECRET}" -eq 0 ]; then echo "❌ ERROR: KUBECTLSECRET is empty!" exit 1 fi # Try to decode as base64 first, if that fails, use as plain text if echo "$KUBECTLSECRET" | base64 -d > ~/.kube/config 2>/dev/null; then echo "βœ… KUBECTLSECRET decoded as base64" else echo "⚠️ KUBECTLSECRET is not base64, using as plain text" echo "$KUBECTLSECRET" > ~/.kube/config fi echo "πŸ“ kubeconfig created at ~/.kube/config" chmod 600 ~/.kube/config # Debug kubeconfig content (without sensitive data) echo "πŸ” Debugging kubeconfig structure..." echo "File size: $(wc -c < ~/.kube/config) bytes" echo "First few lines of kubeconfig (structure check):" head -20 ~/.kube/config | grep -E "(apiVersion|kind|clusters|contexts|users|current-context)" || echo "No standard kubeconfig structure found" echo "Checking for current-context:" grep "current-context:" ~/.kube/config || echo "❌ No current-context found" echo "Checking for clusters:" grep -A 2 "clusters:" ~/.kube/config || echo "❌ No clusters found" echo "Checking for users:" grep -A 2 "users:" ~/.kube/config || echo "❌ No users found" # Fix TLS issues by adding insecure-skip-tls-verify to all clusters echo "πŸ”§ Fixing TLS verification for self-signed certificates..." # Get all cluster names and add insecure-skip-tls-verify kubectl config get-clusters | tail -n +2 | while read cluster; do if [ -n "$cluster" ]; then echo "Setting insecure-skip-tls-verify for cluster: $cluster" kubectl config set-cluster "$cluster" --insecure-skip-tls-verify=true fi done echo "βœ… TLS configuration completed" - name: Test kubectl connection run: | kubectl version --client kubectl get nodes - name: Deploy to Production env: FRONTEND_IMAGE: ${{ steps.meta.outputs.frontend_image }} BACKEND_IMAGE: ${{ steps.meta.outputs.backend_image }} BOT_IMAGE: ${{ steps.meta.outputs.bot_image }} run: | echo "πŸš€ Deploying to production..." echo "Frontend image: $FRONTEND_IMAGE" echo "Backend image: $BACKEND_IMAGE" echo "Bot image: $BOT_IMAGE" # Ensure namespace exists kubectl create namespace florale-emotion --dry-run=client -o yaml | kubectl apply -f - # Ensure Harbor registry secret exists kubectl create secret docker-registry harbor-registry-secret \ --docker-server=${{ env.HARBOR_REGISTRY }} \ --docker-username=admin \ --docker-password=Harbor12345 \ --namespace=florale-emotion \ --dry-run=client -o yaml | kubectl apply -f - # Apply frontend deployment und ersetze das Image-Tag mit latest sed "s|__IMAGE_TAG__|latest|g" website/k8s/frontend.yaml | kubectl apply -n florale-emotion -f - # Apply backend deployment mit latest Tag sed "s|__IMAGE_TAG__|latest|g" website/k8s/backend.yaml | kubectl apply -n florale-emotion -f - # Apply social media bot deployment mit latest Tag sed "s|__IMAGE_TAG__|latest|g" website/k8s/bot.yaml | kubectl apply -n florale-emotion -f - # Apply production ingress kubectl apply -f website/k8s/ingress.yaml -n florale-emotion # Wait for rollout echo "⏳ Waiting for rollout to finish..." kubectl rollout status deployment/florale-emotion-frontend -n florale-emotion --timeout=300s echo "βœ… Production deployment complete!" echo "🌐 Website: https://florale-emotion.de" echo "🌐 WWW: https://www.florale-emotion.de" echo "πŸ”§ Backend API: https://api.florale-emotion.de" # Hinweise: # - Angepasst fΓΌr florale-emotion Projekt # - Namespace: florale-emotion # - Dev-Deployment unter dev.florale-emotion.de # - Production unter florale-emotion.de # - UnterstΓΌtzt Frontend, Backend und Social Media Bot # - Robuste Retry-Mechanismen fΓΌr Build und Push # - Eindeutige Tags fΓΌr bessere Nachverfolgung # - Timeout-Schutz fΓΌr alle Deployments