chore: preview ai-chatbot#260 (idle timeout + standby) #1216
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Deploy Infrastructure | |
| on: | |
| push: | |
| branches: | |
| - develop | |
| - main | |
| pull_request: | |
| branches: [develop, main] # Preview deployment on PRs | |
| workflow_dispatch: # Manual trigger | |
| inputs: | |
| environment: | |
| description: 'Environment to deploy' | |
| required: true | |
| default: 'preview' | |
| type: choice | |
| options: | |
| - dev | |
| - preview | |
| - prod | |
| env: | |
| PROJECT_ID: nava-labs | |
| REGION: us-central1 | |
| ARTIFACT_REGISTRY: us-central1-docker.pkg.dev/nava-labs/labs-asp | |
| jobs: | |
| # Determine environment based on branch/trigger | |
| setup: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| environment: ${{ steps.env.outputs.environment }} | |
| should_deploy: ${{ steps.env.outputs.should_deploy }} | |
| steps: | |
| - name: Determine environment | |
| id: env | |
| run: | | |
| if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then | |
| ENVIRONMENT="${{ github.event.inputs.environment }}" | |
| SHOULD_DEPLOY="true" | |
| elif [[ "${{ github.event_name }}" == "pull_request" ]]; then | |
| ENVIRONMENT="preview-pr-${{ github.event.pull_request.number }}" | |
| SHOULD_DEPLOY="true" # PRs deploy to isolated preview environment | |
| elif [[ "${{ github.ref }}" == "refs/heads/main" ]]; then | |
| ENVIRONMENT="prod" | |
| SHOULD_DEPLOY="true" | |
| elif [[ "${{ github.ref }}" == "refs/heads/develop" ]]; then | |
| ENVIRONMENT="dev" | |
| SHOULD_DEPLOY="true" | |
| else | |
| # Any other branch = preview environment with actual deployment | |
| ENVIRONMENT="preview" | |
| SHOULD_DEPLOY="true" | |
| fi | |
| echo "environment=${ENVIRONMENT}" >> $GITHUB_OUTPUT | |
| echo "should_deploy=${SHOULD_DEPLOY}" >> $GITHUB_OUTPUT | |
| echo "Environment: ${ENVIRONMENT} (deploy: ${SHOULD_DEPLOY})" | |
| # Build and push Docker images | |
| build: | |
| runs-on: ubuntu-latest | |
| needs: setup | |
| permissions: | |
| contents: read | |
| id-token: write | |
| outputs: | |
| chatbot_image: ${{ steps.images.outputs.chatbot_image }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| submodules: recursive | |
| - name: Authenticate to Google Cloud | |
| uses: google-github-actions/auth@v2 | |
| with: | |
| service_account: github-actions-deploy@nava-labs.iam.gserviceaccount.com | |
| workload_identity_provider: projects/279889631214/locations/global/workloadIdentityPools/github-actions-pool/providers/github-provider | |
| - name: Set up Cloud SDK | |
| uses: google-github-actions/setup-gcloud@v2 | |
| - name: Configure Docker for Artifact Registry | |
| run: gcloud auth configure-docker us-central1-docker.pkg.dev | |
| - name: Detect project changes | |
| id: changes | |
| uses: dorny/paths-filter@v3 | |
| with: | |
| filters: | | |
| browser: | |
| - 'playwright-mcp/**' | |
| proxy: | |
| - 'browser-ws-proxy/**' | |
| mastra: | |
| - 'Dockerfile' | |
| - 'package.json' | |
| - 'pnpm-lock.yaml' | |
| - 'src/**' | |
| - 'migrations/**' | |
| - '.mastra/**' | |
| chatbot: | |
| - 'Dockerfile.ai-chatbot' | |
| - 'client/**' | |
| terraform: | |
| - 'terraform/**' | |
| - name: Set image tags | |
| id: images | |
| run: | | |
| TAG=${GITHUB_SHA:0:7} | |
| echo "chatbot_image=${ARTIFACT_REGISTRY}/ai-chatbot:${TAG}" >> $GITHUB_OUTPUT | |
| - name: Get PostHog API key | |
| id: posthog_key | |
| run: | | |
| POSTHOG_KEY=$(gcloud secrets versions access latest --secret=posthog-api-key) | |
| echo "::add-mask::$POSTHOG_KEY" | |
| echo "key=$POSTHOG_KEY" >> $GITHUB_OUTPUT | |
| - name: Get Kernel API key | |
| id: kernel_key | |
| run: | | |
| KERNEL_KEY=$(gcloud secrets versions access latest --secret=kernel-api-key) | |
| echo "::add-mask::$KERNEL_KEY" | |
| echo "key=$KERNEL_KEY" >> $GITHUB_OUTPUT | |
| - name: Build and push ai-chatbot image | |
| run: | | |
| # Enable guest login only for preview-pr-* environments | |
| USE_GUEST="false" | |
| if [[ "${{ needs.setup.outputs.environment }}" == preview-pr-* ]]; then | |
| USE_GUEST="true" | |
| fi | |
| docker build \ | |
| --build-arg NEXT_PUBLIC_POSTHOG_KEY=${{ steps.posthog_key.outputs.key }} \ | |
| --build-arg NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com \ | |
| --build-arg KERNEL_API_KEY=${{ steps.kernel_key.outputs.key }} \ | |
| --build-arg USE_GUEST_LOGIN=${USE_GUEST} \ | |
| --build-arg ENVIRONMENT=${{ needs.setup.outputs.environment }} \ | |
| -f Dockerfile.ai-chatbot \ | |
| -t ${{ steps.images.outputs.chatbot_image }} \ | |
| . | |
| docker push ${{ steps.images.outputs.chatbot_image }} | |
| # Deploy infrastructure with Terraform | |
| terraform: | |
| runs-on: ubuntu-latest | |
| needs: [setup, build] | |
| if: needs.setup.outputs.should_deploy == 'true' || github.event_name == 'pull_request' | |
| environment: | |
| name: ${{ needs.setup.outputs.environment }} | |
| url: ${{ steps.tf_outputs.outputs.chatbot_url }} | |
| permissions: | |
| contents: read | |
| id-token: write | |
| pull-requests: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Authenticate to Google Cloud | |
| uses: google-github-actions/auth@v2 | |
| with: | |
| service_account: github-actions-deploy@nava-labs.iam.gserviceaccount.com | |
| workload_identity_provider: projects/279889631214/locations/global/workloadIdentityPools/github-actions-pool/providers/github-provider | |
| - name: Set up Cloud SDK | |
| uses: google-github-actions/setup-gcloud@v2 | |
| - name: Setup Terraform | |
| uses: hashicorp/setup-terraform@v3 | |
| with: | |
| terraform_version: "1.9.0" | |
| # Deploy shared preview VPC first (only for preview environments, before anything else) | |
| - name: Deploy Shared Preview VPC | |
| if: startsWith(needs.setup.outputs.environment, 'preview-') | |
| working-directory: ./terraform/shared-preview-vpc | |
| run: | | |
| echo "🔧 Ensuring shared preview VPC exists..." | |
| # Initialize shared VPC terraform | |
| terraform init | |
| # Check if VPC already exists | |
| if gcloud compute networks describe labs-asp-vpc-preview-shared --project=nava-labs 2>/dev/null; then | |
| echo "✅ Shared preview VPC already exists, skipping creation" | |
| else | |
| echo "🚀 Creating shared preview VPC (first preview deployment)..." | |
| terraform apply -auto-approve | |
| echo "✅ Shared preview VPC created successfully" | |
| fi | |
| - name: Terraform Init | |
| working-directory: ./terraform | |
| run: | | |
| terraform init \ | |
| -backend-config="prefix=terraform/state/${{ needs.setup.outputs.environment }}" | |
| - name: Verify Terraform State | |
| working-directory: ./terraform | |
| run: | | |
| echo "Checking Terraform state for environment: ${{ needs.setup.outputs.environment }}" | |
| terraform state list || echo "No existing state found (expected for first deployment)" | |
| # Establish VPC peering on dev environment deployment | |
| - name: Establish VPC Peering with Shared Preview VPC | |
| if: needs.setup.outputs.environment == 'dev' | |
| working-directory: ./terraform/shared-preview-vpc | |
| run: | | |
| echo "🔗 Ensuring VPC peering between dev and shared preview VPC..." | |
| # Initialize shared VPC terraform | |
| terraform init | |
| # Check if shared preview VPC exists | |
| if gcloud compute networks describe labs-asp-vpc-preview-shared --project=nava-labs 2>/dev/null; then | |
| echo "✅ Shared preview VPC exists, ensuring peering is configured..." | |
| terraform apply -auto-approve | |
| echo "✅ VPC peering established/updated successfully" | |
| else | |
| echo "⚠️ Shared preview VPC does not exist yet (will be created on first preview deployment)" | |
| fi | |
| - name: Set VPC CIDR blocks and Firewall rules | |
| id: vpc_cidrs | |
| run: | | |
| ENV="${{ needs.setup.outputs.environment }}" | |
| # Set VPC CIDR blocks based on environment | |
| # Pattern: 10.X.0.0/16 where X is determined by environment | |
| # Note: Preview environments use shared VPC (10.1.0.0/16) but still need CIDR values for Terraform variables | |
| case ${ENV} in | |
| dev) | |
| # Dev: 10.0.0.0/16 | |
| echo "vpc_cidr_public=10.0.0.0/20" >> $GITHUB_OUTPUT | |
| echo "vpc_cidr_private=10.0.16.0/20" >> $GITHUB_OUTPUT | |
| echo "vpc_cidr_db=10.0.32.0/20" >> $GITHUB_OUTPUT | |
| echo "vpc_connector_cidr=10.0.48.0/28" >> $GITHUB_OUTPUT | |
| # Dev: Allow public access for all services (JSON in single line) | |
| echo 'firewall_rules={"browser_mcp":{"port":8931,"allow_public_access":true,"allowed_ip_ranges":["0.0.0.0/0"]},"browser_streaming":{"port":8933,"allow_public_access":true,"allowed_ip_ranges":["0.0.0.0/0"]},"mastra_api":{"port":4112,"allow_public_access":true,"allowed_ip_ranges":["0.0.0.0/0"]}}' >> $GITHUB_OUTPUT | |
| ;; | |
| preview|preview-*) | |
| # Preview: Uses shared VPC (10.1.0.0/16) - values for reference only | |
| echo "vpc_cidr_public=10.1.0.0/20" >> $GITHUB_OUTPUT | |
| echo "vpc_cidr_private=10.1.16.0/20" >> $GITHUB_OUTPUT | |
| echo "vpc_cidr_db=10.1.32.0/20" >> $GITHUB_OUTPUT | |
| echo "vpc_connector_cidr=10.1.48.0/28" >> $GITHUB_OUTPUT | |
| # Preview: Allow public access for testing (JSON in single line) | |
| echo 'firewall_rules={"browser_mcp":{"port":8931,"allow_public_access":true,"allowed_ip_ranges":["0.0.0.0/0"]},"browser_streaming":{"port":8933,"allow_public_access":true,"allowed_ip_ranges":["0.0.0.0/0"]},"mastra_api":{"port":4112,"allow_public_access":true,"allowed_ip_ranges":["0.0.0.0/0"]}}' >> $GITHUB_OUTPUT | |
| ;; | |
| prod) | |
| # Production: 10.2.0.0/16 | |
| echo "vpc_cidr_public=10.2.0.0/20" >> $GITHUB_OUTPUT | |
| echo "vpc_cidr_private=10.2.16.0/20" >> $GITHUB_OUTPUT | |
| echo "vpc_cidr_db=10.2.32.0/20" >> $GITHUB_OUTPUT | |
| echo "vpc_connector_cidr=10.2.48.0/28" >> $GITHUB_OUTPUT | |
| # Prod: Granular firewall rules per service (JSON in single line) | |
| # TODO: Update with your production IP ranges and ports | |
| echo 'firewall_rules={"browser_mcp":{"port":8931,"allow_public_access":false,"allowed_ip_ranges":["203.0.113.0/24"]},"browser_streaming":{"port":8933,"allow_public_access":false,"allowed_ip_ranges":["203.0.113.0/24","198.51.100.0/24"]},"mastra_api":{"port":4112,"allow_public_access":false,"allowed_ip_ranges":["203.0.113.0/24"]}}' >> $GITHUB_OUTPUT | |
| ;; | |
| *) | |
| # Default to dev range | |
| echo "vpc_cidr_public=10.0.0.0/20" >> $GITHUB_OUTPUT | |
| echo "vpc_cidr_private=10.0.16.0/20" >> $GITHUB_OUTPUT | |
| echo "vpc_cidr_db=10.0.32.0/20" >> $GITHUB_OUTPUT | |
| echo "vpc_connector_cidr=10.0.48.0/28" >> $GITHUB_OUTPUT | |
| # Default: Public access (JSON in single line) | |
| echo 'firewall_rules={"browser_mcp":{"port":8931,"allow_public_access":true,"allowed_ip_ranges":["0.0.0.0/0"]},"browser_streaming":{"port":8933,"allow_public_access":true,"allowed_ip_ranges":["0.0.0.0/0"]},"mastra_api":{"port":4112,"allow_public_access":true,"allowed_ip_ranges":["0.0.0.0/0"]}}' >> $GITHUB_OUTPUT | |
| ;; | |
| esac | |
| echo "VPC CIDRs and Firewall rules configured for ${ENV}" | |
| - name: Terraform Plan | |
| working-directory: ./terraform | |
| run: | | |
| # Enable custom domain only for dev and prod environments | |
| ENABLE_DOMAIN="false" | |
| if [[ "${{ needs.setup.outputs.environment }}" == "dev" ]] || [[ "${{ needs.setup.outputs.environment }}" == "prod" ]]; then | |
| ENABLE_DOMAIN="true" | |
| fi | |
| # Enable guest login only for preview-pr-* environments | |
| USE_GUEST="false" | |
| if [[ "${{ needs.setup.outputs.environment }}" == preview-pr-* ]]; then | |
| USE_GUEST="true" | |
| fi | |
| terraform plan \ | |
| -var="environment=${{ needs.setup.outputs.environment }}" \ | |
| -var="chatbot_image_url=${{ needs.build.outputs.chatbot_image }}" \ | |
| -var="enable_custom_domain=${ENABLE_DOMAIN}" \ | |
| -var="use_guest_login=${USE_GUEST}" \ | |
| -var="vpc_cidr_public=${{ steps.vpc_cidrs.outputs.vpc_cidr_public }}" \ | |
| -var="vpc_cidr_private=${{ steps.vpc_cidrs.outputs.vpc_cidr_private }}" \ | |
| -var="vpc_cidr_db=${{ steps.vpc_cidrs.outputs.vpc_cidr_db }}" \ | |
| -var="vpc_connector_cidr=${{ steps.vpc_cidrs.outputs.vpc_connector_cidr }}" \ | |
| -var='firewall_rules=${{ steps.vpc_cidrs.outputs.firewall_rules }}' \ | |
| -out=tfplan | |
| - name: Terraform Apply | |
| if: needs.setup.outputs.should_deploy == 'true' | |
| working-directory: ./terraform | |
| run: terraform apply -auto-approve tfplan | |
| - name: Get Terraform Outputs | |
| if: needs.setup.outputs.should_deploy == 'true' | |
| working-directory: ./terraform | |
| id: tf_outputs | |
| run: | | |
| echo "chatbot_url=$(terraform output -raw chatbot_public_url)" >> $GITHUB_OUTPUT | |
| echo "custom_domain=$(terraform output -raw custom_domain)" >> $GITHUB_OUTPUT | |
| - name: Comment on PR with deployment info | |
| if: github.event_name == 'pull_request' | |
| uses: actions/github-script@v8 | |
| with: | |
| script: | | |
| const shouldDeploy = '${{ needs.setup.outputs.should_deploy }}' === 'true'; | |
| const customDomain = '${{ steps.tf_outputs.outputs.custom_domain }}'; | |
| const hasCustomDomain = customDomain && customDomain.length > 0; | |
| const body = shouldDeploy | |
| ? `## Preview Deployment Complete | |
| Infrastructure deployed to **${{ needs.setup.outputs.environment }}** environment. | |
| ### Access URLs | |
| - **AI Chatbot (Cloud Run)**: ${{ steps.tf_outputs.outputs.chatbot_url }} | |
| ${hasCustomDomain ? `**Custom Domain**: https://${customDomain}\n\n**Note:** Custom domain SSL certificate may take 5-15 minutes to provision on first deployment.` : '**Note:** Custom domain not configured for preview environments. Use Cloud Run URL to access the application.'}` | |
| : `## Terraform Plan Complete | |
| Infrastructure changes have been planned for **${{ needs.setup.outputs.environment }}** environment. | |
| ### Changes | |
| - Cloud Run services: ai-chatbot | |
| - All services connect to **dev database** and **dev GCS bucket** | |
| **Note:** This is a plan only. Merge to deploy.`; | |
| github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: body | |
| }); | |
| - name: Create deployment summary | |
| if: needs.setup.outputs.should_deploy == 'true' | |
| id: summary | |
| run: | | |
| CUSTOM_DOMAIN="${{ steps.tf_outputs.outputs.custom_domain }}" | |
| cat >> $GITHUB_STEP_SUMMARY <<EOF | |
| ## Deployment Complete: ${{ needs.setup.outputs.environment }} | |
| ### Access URLs | |
| - **AI Chatbot (Cloud Run)**: ${{ steps.tf_outputs.outputs.chatbot_url }} | |
| EOF | |
| if [ -n "$CUSTOM_DOMAIN" ]; then | |
| cat >> $GITHUB_STEP_SUMMARY <<EOF | |
| ### Custom Domain | |
| - **AI Chatbot**: https://$CUSTOM_DOMAIN | |
| **Note:** Custom domain SSL certificate may take 5-15 minutes to provision on first deployment. | |
| EOF | |
| fi | |
| cat >> $GITHUB_STEP_SUMMARY <<EOF | |
| ### Environment | |
| - **Environment**: ${{ needs.setup.outputs.environment }} | |
| EOF | |
| - name: Deployment Summary (console) | |
| if: needs.setup.outputs.should_deploy == 'true' | |
| run: | | |
| CUSTOM_DOMAIN="${{ steps.tf_outputs.outputs.custom_domain }}" | |
| echo "Deployment to ${{ needs.setup.outputs.environment }} complete" | |
| echo "Cloud Run URL: ${{ steps.tf_outputs.outputs.chatbot_url }}" | |
| if [ -n "$CUSTOM_DOMAIN" ]; then | |
| echo "Custom Domain: https://$CUSTOM_DOMAIN" | |
| fi |