Skip to content

fix

fix #461

Workflow file for this run

name: Build and Deploy Major League Github on GCP 🚀 ☁️
on:
push:
branches: [ main ]
workflow_dispatch:
env:
PROJECT_NAME: ${{ secrets.PROJECT_NAME }}
VERSION: v1.0.${{ github.run_number }}
ENVIRONMENT: production
# Google Tag Manager
GTM_ID: ${{ secrets.GTM_ID }}
# GCP Configuration
GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}
GKE_CLUSTER: ${{ secrets.GKE_CLUSTER }}
GKE_ZONE: ${{ secrets.GKE_ZONE }}
# GitHub Container Registry
REGISTRY: ghcr.io
REGISTRY_NAME: ${{ github.repository }}
# Kubernetes namespace (convert repo name to valid namespace)
NAMESPACE: ${{ github.event.repository.name }}
DOMAIN_NAME: ${{ secrets.DOMAIN_NAME }}
DOMAIN_SUFFIX: ${{ secrets.DOMAIN_SUFFIX }}
FORWARD_TO_WWW: ${{ secrets.FORWARD_TO_WWW || 'false' }}
STATIC_IP_NAME: "main-ingress-ip"
STATIC_IP_ADDRESS: ${{ secrets.STATIC_IP_ADDRESS }}
GH_API_TOKENS: ${{ secrets.GH_API_TOKENS }}
# LinkedIn
LINKEDIN_CLIENT_ID: ${{ secrets.LINKEDIN_CLIENT_ID }}
LINKEDIN_CLIENT_SECRET: ${{ secrets.LINKEDIN_CLIENT_SECRET }}
# Resource configuration
BACKEND_REPLICAS: 1
FRONTEND_REPLICAS: 1
# Backend resources (minimal for single-node cluster)
BACKEND_REQUEST_MEMORY: 256Mi
BACKEND_REQUEST_CPU: 50m
BACKEND_LIMIT_MEMORY: 1Gi
BACKEND_LIMIT_CPU: 500m
# Frontend resources (minimal for single-node cluster)
FRONTEND_REQUEST_MEMORY: 64Mi
FRONTEND_REQUEST_CPU: 25m
FRONTEND_LIMIT_MEMORY: 256Mi
FRONTEND_LIMIT_CPU: 200m
# Java settings
JAVA_MAX_HEAP: 1200m
JAVA_MIN_HEAP: 512m
# Redis Configuration
REDIS_HOST: ${{ secrets.REDIS_IP }}
jobs:
build-and-deploy:
runs-on: ubuntu-22.04
permissions:
contents: read
packages: write
deployments: write
steps:
- name: Verify Required Secrets 🔒
run: |
missing_secrets=()
# Core project secrets
if [ -z "${{ secrets.PROJECT_NAME }}" ]; then
missing_secrets+=("PROJECT_NAME")
fi
# GCP Infrastructure secrets
if [ -z "${{ secrets.GCP_PROJECT_ID }}" ]; then
missing_secrets+=("GCP_PROJECT_ID")
fi
if [ -z "${{ secrets.GCP_SA_KEY }}" ]; then
missing_secrets+=("GCP_SA_KEY")
fi
if [ -z "${{ secrets.GKE_CLUSTER }}" ]; then
missing_secrets+=("GKE_CLUSTER")
fi
if [ -z "${{ secrets.GKE_ZONE }}" ]; then
missing_secrets+=("GKE_ZONE")
fi
# Container Registry auto-detected from repository
# DNS secrets
if [ -z "${{ secrets.DNS_PROJECT_ID }}" ]; then
missing_secrets+=("DNS_PROJECT_ID")
fi
if [ -z "${{ secrets.DNS_ZONE }}" ]; then
missing_secrets+=("DNS_ZONE")
fi
# Redis secret
if [ -z "${{ secrets.REDIS_IP }}" ]; then
missing_secrets+=("REDIS_IP")
fi
# Domain secrets
if [ -z "${{ secrets.DOMAIN_NAME }}" ]; then
missing_secrets+=("DOMAIN_NAME")
fi
if [ -z "${{ secrets.DOMAIN_SUFFIX }}" ]; then
missing_secrets+=("DOMAIN_SUFFIX")
fi
# FORWARD_TO_WWW is optional (defaults to false)
# Application secrets
if [ -z "${{ secrets.GH_API_TOKENS }}" ]; then
missing_secrets+=("GH_API_TOKENS")
fi
if [ -z "${{ secrets.LINKEDIN_CLIENT_ID }}" ]; then
missing_secrets+=("LINKEDIN_CLIENT_ID")
fi
if [ -z "${{ secrets.LINKEDIN_CLIENT_SECRET }}" ]; then
missing_secrets+=("LINKEDIN_CLIENT_SECRET")
fi
if [ -z "${{ secrets.GTM_ID }}" ]; then
missing_secrets+=("GTM_ID")
fi
if [ ${#missing_secrets[@]} -ne 0 ]; then
echo "❌ Error: Missing required secrets: ${missing_secrets[*]}"
exit 1
fi
echo "✅ All required secrets are set"
- name: Checkout code 📦
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Google Cloud CLI 🛠️
uses: google-github-actions/auth@v2
with:
credentials_json: ${{ secrets.GCP_SA_KEY }}
- name: Configure gcloud 🔧
uses: google-github-actions/setup-gcloud@v2
- name: Get GKE credentials 🔑
uses: google-github-actions/get-gke-credentials@v2
with:
cluster_name: ${{ env.GKE_CLUSTER }}
location: ${{ env.GKE_ZONE }}
- name: Login to GitHub Container Registry 🐳
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Check for changes 🔍
uses: dorny/paths-filter@v3
id: changes
with:
filters: |
backend:
- 'backend/**'
- 'kubernetes/**/*backend*'
- '.github/workflows/deploy.yml'
cache_updater:
- 'backend/**'
- 'kubernetes/**/*cache*'
- '.github/workflows/deploy.yml'
frontend:
- 'frontend/**'
- 'kubernetes/**/*frontend*'
- '.github/workflows/deploy.yml'
- name: Set Webapp Environment Variables 🎨
run: |
# Set OG Meta Tags environment variables
echo "OG_TITLE=Major League GitHub" >> $GITHUB_ENV
echo "OG_DESCRIPTION=GitHub Scouting Report: Major League Edition" >> $GITHUB_ENV
echo "OG_TYPE=website" >> $GITHUB_ENV
echo "OG_IMAGE_URL=/og-image.png" >> $GITHUB_ENV
echo "OG_SITE_NAME=Major League GitHub" >> $GITHUB_ENV
- name: Build and push backend service image 🏗️
if: steps.changes.outputs.backend == 'true'
run: |
echo "🚀 Building backend service image..."
docker build \
--build-arg PROFILE=backend-service \
-t ${{ env.REGISTRY }}/${{ env.REGISTRY_NAME }}/backend-service:${{ env.VERSION }} \
-t ${{ env.REGISTRY }}/${{ env.REGISTRY_NAME }}/backend-service:latest \
backend
echo "🚀 Pushing backend service images..."
docker push ${{ env.REGISTRY }}/${{ env.REGISTRY_NAME }}/backend-service:${{ env.VERSION }}
docker push ${{ env.REGISTRY }}/${{ env.REGISTRY_NAME }}/backend-service:latest
- name: Skip backend service build 🏗️
if: steps.changes.outputs.backend == 'false'
run: echo "⏩ Skipping backend service build - no changes detected"
- name: Build and push cache updater image 🏗️
if: steps.changes.outputs.cache_updater == 'true'
run: |
echo "🚀 Building cache updater image..."
docker build \
--build-arg PROFILE=cache-updater \
-t ${{ env.REGISTRY }}/${{ env.REGISTRY_NAME }}/cache-updater:${{ env.VERSION }} \
-t ${{ env.REGISTRY }}/${{ env.REGISTRY_NAME }}/cache-updater:latest \
backend
echo "🚀 Pushing cache updater images..."
docker push ${{ env.REGISTRY }}/${{ env.REGISTRY_NAME }}/cache-updater:${{ env.VERSION }}
docker push ${{ env.REGISTRY }}/${{ env.REGISTRY_NAME }}/cache-updater:latest
- name: Skip cache updater build 🏗️
if: steps.changes.outputs.cache_updater == 'false'
run: echo "⏩ Skipping cache updater build - no changes detected"
- name: Build and push frontend image 🎨
if: steps.changes.outputs.frontend == 'true'
env:
OG_TITLE: ${{ env.OG_TITLE }}
OG_DESCRIPTION: ${{ env.OG_DESCRIPTION }}
OG_TYPE: ${{ env.OG_TYPE }}
OG_IMAGE_URL: ${{ env.OG_IMAGE_URL }}
OG_SITE_NAME: ${{ env.OG_SITE_NAME }}
WEBAPP_EXTRA_BUTTON_LINK: ${{ env.WEBAPP_EXTRA_BUTTON_LINK }}
WEBAPP_EXTRA_BUTTON_TEXT: ${{ env.WEBAPP_EXTRA_BUTTON_TEXT }}
run: |
echo "🚀 Building frontend image..."
# Determine redirect configuration
ENABLE_REDIRECT="false"
ROOT_DOMAIN=""
TARGET_DOMAIN="${{ env.DOMAIN_NAME }}.${{ env.DOMAIN_SUFFIX }}"
if [ "${{ env.FORWARD_TO_WWW }}" = "true" ]; then
# Check if DOMAIN_NAME is a subdomain (doesn't contain dots)
if [[ "${{ env.DOMAIN_NAME }}" != *"."* ]]; then
# This is a simple subdomain like "www", so enable redirect
ENABLE_REDIRECT="true"
ROOT_DOMAIN="${{ env.DOMAIN_SUFFIX }}"
echo "📍 Enabling redirect: $ROOT_DOMAIN → $TARGET_DOMAIN"
else
echo "⚠️ DOMAIN_NAME contains dots, assuming complex subdomain - no redirect"
fi
fi
docker build frontend \
--build-arg NODE_ENV=production \
--build-arg BACKEND_API_URL="/" \
--build-arg GTM_ID="${{ env.GTM_ID }}" \
--build-arg OG_TITLE="${{ env.OG_TITLE }}" \
--build-arg OG_DESCRIPTION="${{ env.OG_DESCRIPTION }}" \
--build-arg OG_TYPE="${{ env.OG_TYPE }}" \
--build-arg OG_IMAGE_URL="${{ env.OG_IMAGE_URL }}" \
--build-arg OG_URL="https://$TARGET_DOMAIN" \
--build-arg OG_SITE_NAME="${{ env.OG_SITE_NAME }}" \
--build-arg WEBAPP_EXTRA_BUTTON_LINK="${{ env.WEBAPP_EXTRA_BUTTON_LINK }}" \
--build-arg WEBAPP_EXTRA_BUTTON_TEXT="${{ env.WEBAPP_EXTRA_BUTTON_TEXT }}" \
--build-arg ENABLE_REDIRECT="$ENABLE_REDIRECT" \
--build-arg ROOT_DOMAIN="$ROOT_DOMAIN" \
--build-arg TARGET_DOMAIN="$TARGET_DOMAIN" \
-t "${{ env.REGISTRY }}/${{ env.REGISTRY_NAME }}/frontend:${{ env.VERSION }}" \
-t "${{ env.REGISTRY }}/${{ env.REGISTRY_NAME }}/frontend:latest"
echo "🚀 Pushing frontend images..."
docker push ${{ env.REGISTRY }}/${{ env.REGISTRY_NAME }}/frontend:${{ env.VERSION }}
docker push ${{ env.REGISTRY }}/${{ env.REGISTRY_NAME }}/frontend:latest
- name: Skip frontend build 🎨
if: steps.changes.outputs.frontend == 'false'
run: echo "⏩ Skipping frontend build - no changes detected"
- name: Create Kubernetes namespace and secrets 🔐
run: |
# Create namespace
kubectl create namespace ${{ env.NAMESPACE }} --dry-run=client -o yaml | kubectl apply -f -
# Create application secrets
kubectl create secret generic app-secrets \
--namespace=${{ env.NAMESPACE }} \
--from-literal=github.tokens="${{ env.GH_API_TOKENS }}" \
--from-literal=linkedin.client.id="${{ env.LINKEDIN_CLIENT_ID }}" \
--from-literal=linkedin.client.secret="${{ env.LINKEDIN_CLIENT_SECRET }}" \
--dry-run=client -o yaml | kubectl apply -f -
echo "✅ Kubernetes secrets created successfully"
- name: Cleanup GKE Resources 🧹
run: |
echo "🧹 Cleaning up all existing resources in namespace ${{ env.NAMESPACE }}..."
# Clean up all resources in namespace (except secrets and configmaps we'll recreate)
kubectl delete all --all -n ${{ env.NAMESPACE }} --ignore-not-found=true || echo "No standard resources to clean up"
kubectl delete ingress --all -n ${{ env.NAMESPACE }} --ignore-not-found=true || echo "No ingress resources to clean up"
kubectl delete backendconfig --all -n ${{ env.NAMESPACE }} --ignore-not-found=true || echo "No backendconfig resources to clean up"
# Wait for NEG cleanup to complete (they take time to detach from load balancers)
echo "⏳ Waiting for NEG cleanup to complete..."
sleep 30
echo "✅ Cleanup completed successfully"
- name: Deploy to GKE 🚀
env:
REGISTRY_URL: ${{ env.REGISTRY }}/${{ env.REGISTRY_NAME }}
DOMAIN_SUFFIX: ${{ env.DOMAIN_SUFFIX }}
DOMAIN_NAME: ${{ env.DOMAIN_NAME }}
ENVIRONMENT: ${{ env.ENVIRONMENT }}
VERSION: ${{ env.VERSION }}
BACKEND_SERVICE_NAME: "backend-service"
CACHE_UPDATER_NAME: "cache-updater"
BACKEND_SERVICE_PORT: "8080"
CACHE_UPDATER_PORT: "8081"
FRONTEND_PORT: "80"
STATIC_IP_ADDRESS: ${{ secrets.STATIC_IP_ADDRESS }}
run: |
echo "🚀 Processing and applying Kubernetes configurations..."
# Always use 'latest' tag for active K8s deployments for simplicity
BACKEND_VERSION="latest"
CACHE_UPDATER_VERSION="latest"
FRONTEND_VERSION="latest"
echo "🏷️ Using 'latest' tags for all K8s deployments"
# Create temporary directory
mkdir -p processed_k8s
# Process config files
echo "🚀 Processing config.yaml..."
envsubst < kubernetes/base/config.yaml > processed_k8s/config.yaml
kubectl apply -n ${{ env.NAMESPACE }} -f processed_k8s/config.yaml
# Process and apply backend service
echo "🚀 Processing backend service..."
SERVICE_NAME=backend-service \
SERVER_PORT=8080 \
VERSION=$BACKEND_VERSION \
envsubst < kubernetes/base/backend-service.yaml > processed_k8s/backend-service.yaml
kubectl apply --server-side=true --force-conflicts -n ${{ env.NAMESPACE }} -f processed_k8s/backend-service.yaml
# Process and apply cache updater
echo "🚀 Processing cache updater..."
SERVICE_NAME=cache-updater \
SERVER_PORT=8081 \
VERSION=$CACHE_UPDATER_VERSION \
envsubst < kubernetes/base/cache-updater-service.yaml > processed_k8s/cache-updater.yaml
kubectl apply --server-side=true --force-conflicts -n ${{ env.NAMESPACE }} -f processed_k8s/cache-updater.yaml
# Process and apply frontend (creates frontend-service as ClusterIP with NEG)
echo "🚀 Processing frontend..."
# Determine redirect configuration for runtime
ENABLE_REDIRECT="false"
ROOT_DOMAIN=""
if [ "${{ env.FORWARD_TO_WWW }}" = "true" ]; then
# Check if DOMAIN_NAME is a subdomain (doesn't contain dots)
if [[ "${{ env.DOMAIN_NAME }}" != *"."* ]]; then
# This is a simple subdomain like "www", so enable redirect
ENABLE_REDIRECT="true"
ROOT_DOMAIN="${{ env.DOMAIN_SUFFIX }}"
echo "📍 Frontend nginx redirect: $ROOT_DOMAIN → ${{ env.DOMAIN_NAME }}.${{ env.DOMAIN_SUFFIX }}"
else
echo "⚠️ DOMAIN_NAME contains dots, assuming complex subdomain - no redirect"
fi
fi
VERSION=$FRONTEND_VERSION \
ENABLE_REDIRECT="$ENABLE_REDIRECT" \
ROOT_DOMAIN="$ROOT_DOMAIN" \
envsubst < kubernetes/base/frontend.yaml > processed_k8s/frontend.yaml
kubectl apply --server-side=true --force-conflicts -n ${{ env.NAMESPACE }} -f processed_k8s/frontend.yaml
echo "✅ Frontend applied successfully"
echo "✅ All resources cleanup completed"
# Check deployment status immediately after creation
echo "🔍 Immediate deployment status check..."
kubectl get deployments -n ${{ env.NAMESPACE }} -o wide
kubectl get pods -n ${{ env.NAMESPACE }} -o wide
kubectl get events -n ${{ env.NAMESPACE }} --sort-by='.lastTimestamp' | tail -20
echo "🔍 Cache-updater specific details:"
kubectl describe deployment/cache-updater -n ${{ env.NAMESPACE }}
echo "🔍 Cache-updater pod details:"
kubectl describe pods -n ${{ env.NAMESPACE }} -l app=cache-updater
echo "🔍 Cache-updater logs (if any):"
kubectl logs -n ${{ env.NAMESPACE }} -l app=cache-updater --tail=50 || echo "No logs yet"
# Wait for deployments to be ready
echo "🚀 Now waiting for deployments to be ready..."
kubectl wait --for=condition=available deployment/backend-service -n ${{ env.NAMESPACE }} --timeout=300s
kubectl wait --for=condition=available deployment/cache-updater -n ${{ env.NAMESPACE }} --timeout=300s
kubectl wait --for=condition=available deployment/frontend -n ${{ env.NAMESPACE }} --timeout=300s
# Create static IP if it doesn't exist
echo "🚀 Ensuring static IP exists..."
if ! gcloud compute addresses describe main-ingress-ip --global >/dev/null 2>&1; then
echo "Creating static IP main-ingress-ip with address ${STATIC_IP_ADDRESS}..."
gcloud compute addresses create main-ingress-ip --global --addresses=${STATIC_IP_ADDRESS}
else
echo "Static IP main-ingress-ip already exists"
fi
# Note: Root domain redirect is now handled by nginx (not ingress)
if [ "${{ env.FORWARD_TO_WWW }}" = "true" ]; then
echo "🔄 FORWARD_TO_WWW enabled - root domain redirect handled by nginx"
fi
# Create ingress AFTER all services exist with static IP
echo "🚀 Processing ingress after services are ready..."
# Process ingress configuration
# Determine redirect configuration for ingress
ROOT_DOMAIN_CERT=""
ROOT_DOMAIN_RULE=""
if [ "${{ env.FORWARD_TO_WWW }}" = "true" ]; then
if [[ "${{ env.DOMAIN_NAME }}" != *"."* ]]; then
# Add root domain to certificate and ingress rules
ROOT_DOMAIN_CERT=$'\n - ${{ env.DOMAIN_SUFFIX }}'
ROOT_DOMAIN_RULE=$'\n - host: ${{ env.DOMAIN_SUFFIX }}\n http:\n paths:\n - path: /\n pathType: Prefix\n backend:\n service:\n name: frontend-service\n port:\n number: 80'
echo "📍 Adding root domain ${{ env.DOMAIN_SUFFIX }} to ingress and SSL certificate"
fi
fi
DOMAIN_NAME="${{ env.DOMAIN_NAME }}" \
DOMAIN_SUFFIX="${{ env.DOMAIN_SUFFIX }}" \
ROOT_DOMAIN_CERT="$ROOT_DOMAIN_CERT" \
ROOT_DOMAIN_RULE="$ROOT_DOMAIN_RULE" \
envsubst < kubernetes/base/ingress.yaml > processed_k8s/ingress.yaml
# Check if ManagedCertificate already exists and validate domain
if kubectl get managedcertificate main-ingress-ssl-cert -n ${{ env.NAMESPACE }} >/dev/null 2>&1; then
CERT_STATUS=$(kubectl get managedcertificate main-ingress-ssl-cert -n ${{ env.NAMESPACE }} -o jsonpath='{.status.certificateStatus}' 2>/dev/null || echo "Unknown")
CURRENT_DOMAINS=$(kubectl get managedcertificate main-ingress-ssl-cert -n ${{ env.NAMESPACE }} -o jsonpath='{.spec.domains[*]}' 2>/dev/null || echo "")
NEW_DOMAIN="${{ env.DOMAIN_NAME }}.${{ env.DOMAIN_SUFFIX }}"
echo "🔍 Certificate status: $CERT_STATUS"
echo "🔍 Current certificate domains: $CURRENT_DOMAINS"
echo "🔍 New target domain: $NEW_DOMAIN"
# Check if the new domain is already in the certificate
DOMAIN_EXISTS="false"
for domain in $CURRENT_DOMAINS; do
if [ "$domain" = "$NEW_DOMAIN" ]; then
DOMAIN_EXISTS="true"
break
fi
done
if [ "$DOMAIN_EXISTS" != "true" ]; then
echo "🔄 Domain '$NEW_DOMAIN' not found in certificate domains [$CURRENT_DOMAINS] - recreating certificate"
echo "🗑️ Deleting existing ManagedCertificate..."
kubectl delete managedcertificate main-ingress-ssl-cert -n ${{ env.NAMESPACE }} --ignore-not-found=true
echo "⏳ Waiting for certificate cleanup..."
sleep 10
echo "🆕 Creating new ManagedCertificate for domain: $NEW_DOMAIN"
kubectl apply -n ${{ env.NAMESPACE }} -f processed_k8s/ingress.yaml
echo "📊 New certificate status:"
kubectl get managedcertificate main-ingress-ssl-cert -n ${{ env.NAMESPACE }} -o yaml | grep -A 10 "spec:" || echo "Certificate created, status pending..."
else
echo "✅ ManagedCertificate already contains target domain '$NEW_DOMAIN' (status: $CERT_STATUS) - skipping recreation"
# Apply only the ingress part, not the ManagedCertificate
kubectl apply -f processed_k8s/ingress.yaml --selector='!networking.gke.io/managed-certificates' -n ${{ env.NAMESPACE }} || \
kubectl apply -f <(sed '/^apiVersion: networking.gke.io\/v1/,/^---$/d' processed_k8s/ingress.yaml) -n ${{ env.NAMESPACE }}
fi
else
echo "🆕 ManagedCertificate doesn't exist - creating new one"
kubectl apply -n ${{ env.NAMESPACE }} -f processed_k8s/ingress.yaml
fi
# Cleanup
rm -rf processed_k8s
echo "✅ Deployment completed successfully"
- name: Verify Deployment 🔍
run: |
echo "🔍 Checking deployment status..."
echo "Pod Status:"
kubectl -n ${{ env.NAMESPACE }} get pods -o wide
echo "Service Status:"
kubectl -n ${{ env.NAMESPACE }} get services
echo "LoadBalancer Status:"
kubectl -n ${{ env.NAMESPACE }} get ingress main-ingress -o wide
echo "SSL Certificate Status:"
kubectl -n ${{ env.NAMESPACE }} get managedcertificate main-ingress-ssl-cert -o wide || echo "No managed certificate found"
# Use static IP instead of waiting for dynamic assignment
LB_IP="${{ env.STATIC_IP_ADDRESS }}"
echo "✅ Using static IP: ${LB_IP}"
echo "🌐 Frontend URL: http://${LB_IP}/"
echo "🌐 Backend API URL: http://${LB_IP}/api/actuator/health"
echo "✅ Migration Complete - Proper Kubernetes Architecture!"
echo "🌐 Single External Gateway: http://${LB_IP}"
echo " - Frontend: http://${LB_IP}/"
echo " - API: http://${LB_IP}/api"
echo ""
echo "🔒 Internal Services (ClusterIP only):"
echo " - frontend-service:80 (internal)"
echo " - backend-service-service:8080 (internal)"
echo ""
if [ "${{ env.FORWARD_TO_WWW }}" = "true" ]; then
echo "🌍 Primary Domain: https://${{ env.DOMAIN_NAME }}.${{ env.DOMAIN_SUFFIX }}"
echo "🔄 Root Domain Redirect: https://${{ env.DOMAIN_SUFFIX }} → https://${{ env.DOMAIN_NAME }}.${{ env.DOMAIN_SUFFIX }}"
else
echo "🌍 Domain: https://${{ env.DOMAIN_NAME }}.${{ env.DOMAIN_SUFFIX }}"
fi
- name: Update DNS Record (Cross-Project) 🌍
run: |
LB_IP="${{ env.STATIC_IP_ADDRESS }}"
echo "🌍 Updating DNS record for ${{ env.DOMAIN_NAME }}.${{ env.DOMAIN_SUFFIX }} to ${LB_IP}"
# DNS project details
DNS_PROJECT_ID="${{ secrets.DNS_PROJECT_ID }}"
DNS_ZONE="${{ secrets.DNS_ZONE }}"
# Set project context to DNS project
gcloud config set project $DNS_PROJECT_ID
# Debug: List all DNS zones in the project
echo "🔍 Available DNS zones in project $DNS_PROJECT_ID:"
gcloud dns managed-zones list --project=$DNS_PROJECT_ID --format="table(name,dnsName)" || echo "No zones found or permission denied"
echo "🔍 Looking for DNS zone: $DNS_ZONE"
# Check if DNS zone exists
if ! gcloud dns managed-zones describe $DNS_ZONE --project=$DNS_PROJECT_ID --format="value(name)" > /dev/null 2>&1; then
echo "❌ Cloud DNS zone '$DNS_ZONE' not found in project $DNS_PROJECT_ID"
echo "🌍 FALLBACK - Manual DNS Update Required:"
echo "Load Balancer IP: ${LB_IP}"
echo "Go to: https://console.cloud.google.com/net-services/dns/zones?project=$DNS_PROJECT_ID"
exit 1
fi
# Get existing A record if it exists
EXISTING_IP=$(gcloud dns record-sets list --zone=$DNS_ZONE --project=$DNS_PROJECT_ID --name="${{ env.DOMAIN_NAME }}.${{ env.DOMAIN_SUFFIX }}." --type=A --format="value(rrdatas[0])" 2>/dev/null | head -1 || echo "")
if [ ! -z "$EXISTING_IP" ] && [ "$EXISTING_IP" != "null" ] && [ "$EXISTING_IP" != "$LB_IP" ]; then
echo "🌍 Updating existing A record from $EXISTING_IP to $LB_IP"
# Get the existing record with full details including TTL
EXISTING_TTL=$(gcloud dns record-sets list --zone=$DNS_ZONE --project=$DNS_PROJECT_ID --name="${{ env.DOMAIN_NAME }}.${{ env.DOMAIN_SUFFIX }}." --type=A --format="value(ttl)" 2>/dev/null | head -1)
if [ ! -z "$EXISTING_TTL" ] && [ "$EXISTING_TTL" != "null" ]; then
echo "Found existing record: TTL=$EXISTING_TTL, IP=$EXISTING_IP"
# Use transaction to update the record with exact existing values
gcloud dns record-sets transaction start --zone=$DNS_ZONE --project=$DNS_PROJECT_ID
gcloud dns record-sets transaction remove "$EXISTING_IP" \
--name="${{ env.DOMAIN_NAME }}.${{ env.DOMAIN_SUFFIX }}." \
--ttl=$EXISTING_TTL \
--type=A \
--zone=$DNS_ZONE --project=$DNS_PROJECT_ID
gcloud dns record-sets transaction add "$LB_IP" \
--name="${{ env.DOMAIN_NAME }}.${{ env.DOMAIN_SUFFIX }}." \
--ttl=300 \
--type=A \
--zone=$DNS_ZONE --project=$DNS_PROJECT_ID
gcloud dns record-sets transaction execute --zone=$DNS_ZONE --project=$DNS_PROJECT_ID
else
echo "Could not find existing record details, will create new record instead"
# Fall back to creating new record
gcloud dns record-sets transaction start --zone=$DNS_ZONE --project=$DNS_PROJECT_ID
gcloud dns record-sets transaction add "$LB_IP" \
--name="${{ env.DOMAIN_NAME }}.${{ env.DOMAIN_SUFFIX }}." \
--ttl=300 \
--type=A \
--zone=$DNS_ZONE --project=$DNS_PROJECT_ID
gcloud dns record-sets transaction execute --zone=$DNS_ZONE --project=$DNS_PROJECT_ID
fi
elif [ -z "$EXISTING_IP" ] || [ "$EXISTING_IP" = "null" ]; then
echo "🌍 Creating new A record pointing to $LB_IP"
# Create new A record
gcloud dns record-sets transaction start --zone=$DNS_ZONE --project=$DNS_PROJECT_ID
gcloud dns record-sets transaction add "$LB_IP" \
--name="${{ env.DOMAIN_NAME }}.${{ env.DOMAIN_SUFFIX }}." \
--ttl=300 \
--type=A \
--zone=$DNS_ZONE --project=$DNS_PROJECT_ID
gcloud dns record-sets transaction execute --zone=$DNS_ZONE --project=$DNS_PROJECT_ID
else
echo "🌍 DNS record already points to correct IP: $LB_IP"
fi
echo "✅ DNS record updated successfully"
# Note: Root domain redirect is now handled entirely by nginx
if [ "${{ env.FORWARD_TO_WWW }}" = "true" ]; then
echo "ℹ️ FORWARD_TO_WWW enabled - redirect logic handled by nginx in frontend container"
else
echo "ℹ️ FORWARD_TO_WWW disabled - no redirect configured"
fi
echo "🌍 Domain will be accessible at: https://${{ env.DOMAIN_NAME }}.${{ env.DOMAIN_SUFFIX }}"
echo "⏳ DNS propagation may take 5-15 minutes"
# Switch back to original project
gcloud config set project ${{ env.GCP_PROJECT_ID }}
- name: Create GitHub Deployment 📦
uses: chrnorm/deployment-action@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
environment: production
initial-status: success
environment-url: https://${{ env.DOMAIN_NAME }}.${{ env.DOMAIN_SUFFIX }}
- name: Cleanup old images 🧹
if: always()
uses: dataaxiom/ghcr-cleanup-action@v1
continue-on-error: true
with:
owner: ${{ github.repository_owner }}
package: ${{ github.event.repository.name }}/backend-service
delete-ghost-images: true
delete-tags: "*"
exclude-tags: latest,${{ env.VERSION }}
- name: Cleanup old images 🧹
if: always()
uses: dataaxiom/ghcr-cleanup-action@v1
continue-on-error: true
with:
owner: ${{ github.repository_owner }}
package: ${{ github.event.repository.name }}/cache-updater
delete-ghost-images: true
delete-tags: "*"
exclude-tags: latest,${{ env.VERSION }}
- name: Cleanup old images 🧹
if: always()
uses: dataaxiom/ghcr-cleanup-action@v1
continue-on-error: true
with:
owner: ${{ github.repository_owner }}
package: ${{ github.event.repository.name }}/frontend
delete-ghost-images: true
delete-tags: "*"
exclude-tags: latest,${{ env.VERSION }}