Skip to content

chore: preview ai-chatbot#260 (idle timeout + standby) #1216

chore: preview ai-chatbot#260 (idle timeout + standby)

chore: preview ai-chatbot#260 (idle timeout + standby) #1216

Workflow file for this run

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