Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ __pycache__
**/.pytest_cache
**/.mypy_cache
infrastructure/terraform/.terraform
infrastructure/terraform-dev/.terraform
*.tfstate
*.tfstate.*
# Secrets: provided at runtime (Kubernetes secret mount / docker-compose bind mount), never baked into the image
env
.env*
service-account.json
infrastructure/terraform/tfplan
infrastructure/terraform-dev/tfplan
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ get-pip.py
hf_cache/
infrastructure/deploy.ps1
infrastructure/terraform/tfplan
infrastructure/terraform-dev/.terraform
infrastructure/terraform-dev/errored.tfstate
infrastructure/terraform-dev/tfplan

# Secrets: never commit environment files (env was leaked publicly on 2026-10-19)
env
Expand Down
1 change: 1 addition & 0 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ async def lifespan(app: FastAPI):
"https://veracity-eval-frontend-git-feature-open-f37c90-complex-data-lab.vercel.app",
"https://veracity-eval-frontend-git-g0frontend-veracity-s-projects.vercel.app",
"https://veracity-eval-frontend-git-g0frontendmod-veracity-s-projects.vercel.app",
"https://veracity-eval-frontend-git-dev-env-veracity-s-projects.vercel.app",
],
allow_credentials=True,
allow_methods=["*"],
Expand Down
88 changes: 88 additions & 0 deletions docs/dev-environment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Dev environment

A copy of production for end-to-end testing, served at `https://api-dev.veri-fact.ai`.

It lives in the same GCP project (`misinformation-mitigation`). Everything else is separate, with `dev` in each GCP resource name:
- VPC
- GKE cluster
- Cloud SQL instance
- static IP
- certificate
- Terraform state (`gs://misinformation-mitigation-terraform-state/terraform/dev`)

Kubernetes objects inside the dev cluster (namespace, deployment, ingress) keep prod's names, so the same `kubectl` commands work in both.

Shared with prod:
- the `veri-fact.ai` domain. Its DNS is hosted on Cloudflare, where dev has its own `api-dev` A record, added by hand. Terraform also writes an `api-dev` record to the `veri-fact-ai-zone` Cloud DNS zone, but that zone isn't the one the domain uses, so the record has no effect.
- the Artifact Registry, so a commit tested in dev is the exact image that later ships to prod
- the Vertex service-account key (`service-account.json`)

| | Prod | Dev |
|---|---|---|
| Terraform | `infrastructure/terraform` | `infrastructure/terraform-dev` |
| Secrets file | `.env` | the same `.env` (optional `DEV_DB_PASSWORD`) |
| Deployed from | `main` | the `dev-env` branch only |
| Cluster | `misinformation-mitigation-gke-cluster` | `misinformation-mitigation-dev-cluster` |
| API | `api.veri-fact.ai` | `api-dev.veri-fact.ai` |
| Database | prod data | starts empty; migrations run on pod start |

The two Terraform roots are copies of each other. When you change resources in one, make the same change in the other.

## First-time setup

1. **Put `service-account.json` in the repository root.** This is the Vertex key file used for local development. The script stops before changing anything if it is missing.
2. **Optional: give dev its own database password.** Add `DEV_DB_PASSWORD=...` to the same `.env`. If you leave it out, dev uses the prod value of `TF_VAR_db_password`. Dev has its own database instance either way.
3. **Deploy from the `dev-env` branch:**
```bash
git switch dev-env
DEPLOY_ENV=dev ./infrastructure/deploy.sh
```
- On the first run, Terraform first shows a plan for just the cluster and asks for `yes`. Then the script plans everything else and asks again. Both plans should show only creates.
- As with prod, Terraform asks for `auth0_client_id` and `auth0_client_secret` unless they are set; the app doesn't use them.
- The script then creates the `vertex-ai-credentials` secret in the dev cluster from the repo's local `service-account.json`, the same key file used for local development. It does this only when the secret doesn't exist yet.
4. **Add the DNS record in Cloudflare.** Get the dev static IP:
```bash
terraform -chdir=infrastructure/terraform-dev output api_ip_address
```
In the Cloudflare dashboard for `veri-fact.ai`, add an `A` record named `api-dev` pointing to that IP, with proxy status **DNS only** (grey cloud). Google can't issue the certificate while the record is proxied. Check it with `dig +short api-dev.veri-fact.ai`, which should print the static IP and not a Cloudflare `104.x` or `172.x` address.
5. **Wait for the certificate.** Google needs about 15–60 minutes after DNS resolves to issue it. Until then the health check at the end of the script fails. Check progress with:
```bash
kubectl -n misinformation-mitigation describe managedcertificate misinformation-mitigation-cert
```
Once it says `Active`, run the deploy again to confirm the health check passes.

## Day-to-day

- **Deploy dev.** Commit and push to `dev-env`, then run the command below. The script refuses dev deploys from any other branch.
```bash
DEPLOY_ENV=dev ./infrastructure/deploy.sh
```
- **Roll back** to an earlier image:
```bash
DEPLOY_ENV=dev DEPLOY_TAG=<sha> ./infrastructure/deploy.sh
```
- **Prod is unchanged.** Running `./infrastructure/deploy.sh` without `DEPLOY_ENV` deploys prod exactly as before.

## Dev frontend

The frontend reads `NEXT_PUBLIC_API_URL`, and falls back to `https://api.veri-fact.ai` when it is unset.

1. **Vercel.** On the `dev-env` branch of `veracity-eval-frontend`, set these Preview environment variables, scoped to that branch:
- `NEXT_PUBLIC_API_URL=https://api-dev.veri-fact.ai`
- `AUTH0_BASE_URL=<the branch's stable URL>`, for example `https://veracity-eval-frontend-git-dev-env-<team>.vercel.app`
- All other Auth0 variables stay the same as prod.
2. **Auth0 dashboard.** Add the dev frontend URL to the application's:
- Allowed Callback URLs (`<url>/api/auth/callback`)
- Allowed Logout URLs
- Allowed Web Origins
3. **CORS.** Add the dev frontend's URL to the `allow_origins` list in `app/main.py`, the same way the other Vercel branch URLs are listed, and redeploy dev.

## Costs and teardown

Dev runs one e2-medium node (autoscaling to at most 2), a `db-g1-small` Cloud SQL instance, an HTTPS load balancer and Cloud NAT.

To remove everything, run the command below. Terraform asks for each variable it doesn't find; for a destroy the values aren't used, so any value works. It deletes only dev resources, including the dev database and its data.
```bash
terraform -chdir=infrastructure/terraform-dev destroy
```
Then delete the `api-dev` record in Cloudflare by hand; Terraform doesn't manage it.
38 changes: 37 additions & 1 deletion infrastructure/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@ REGISTRY="${REGION}-docker.pkg.dev/${PROJECT_ID}/misinformation-mitigation"
TERRAFORM_DIR="./infrastructure/terraform"
HEALTH_URL="https://api.veri-fact.ai/v1/health"

# Dev environment overrides. Usage: DEPLOY_ENV=dev ./infrastructure/deploy.sh (from the dev-env branch)
DEPLOY_ENV="${DEPLOY_ENV:-prod}"
if [ "$DEPLOY_ENV" = "dev" ]; then
CLUSTER_NAME="misinformation-mitigation-dev-cluster"
TERRAFORM_DIR="./infrastructure/terraform-dev"
HEALTH_URL="https://api-dev.veri-fact.ai/v1/health"
# Dev has its own database; give it its own password when DEV_DB_PASSWORD is set
export TF_VAR_db_password="${DEV_DB_PASSWORD:-${TF_VAR_db_password:-}}"
elif [ "$DEPLOY_ENV" != "prod" ]; then
echo "ERROR: DEPLOY_ENV must be 'prod' or 'dev', not '$DEPLOY_ENV'."
exit 1
fi

# Fall back to the app's own variable names so the same keys need not be duplicated in .env
export TF_VAR_serper_api_key="${TF_VAR_serper_api_key:-${SERPER_API_KEY:-}}"
export TF_VAR_together_api_key="${TF_VAR_together_api_key:-${TOGETHER_API_KEY:-}}"
Expand All @@ -43,6 +56,12 @@ for var in TF_VAR_db_password TF_VAR_serper_api_key TF_VAR_together_api_key TF_V
done
[ "$missing" -eq 0 ] || exit 1

# Dev creates its vertex-ai-credentials secret from this file after Terraform applies; fail before changing anything
if [ "$DEPLOY_ENV" = "dev" ] && [ ! -f service-account.json ]; then
echo "ERROR: service-account.json not found in the repository root. Dev deploys need it to create the vertex-ai-credentials secret."
exit 1
fi

# Function to check if a command is available
check_command() {
if ! command -v "$1" &> /dev/null
Expand All @@ -64,6 +83,11 @@ if [ -n "$(git status --porcelain --untracked-files=no)" ]; then
echo "ERROR: You have uncommitted changes. Commit or stash them before deploying."
exit 1
fi
# Dev is only ever deployed from the dev-env branch
if [ "$DEPLOY_ENV" = "dev" ] && [ "$(git rev-parse --abbrev-ref HEAD)" != "dev-env" ]; then
echo "ERROR: Dev deploys must run from the dev-env branch. Run: git switch dev-env"
exit 1
fi
IMAGE_TAG=$(git rev-parse --short HEAD)
git fetch --quiet origin
if [ -n "${DEPLOY_TAG:-}" ]; then
Expand All @@ -73,7 +97,7 @@ if [ -n "${DEPLOY_TAG:-}" ]; then
elif [ -z "$(git branch -r --contains HEAD)" ]; then
echo "ERROR: Commit $IMAGE_TAG has not been pushed to GitHub. Push it before deploying."
exit 1
elif ! git merge-base --is-ancestor HEAD origin/main; then
elif [ "$DEPLOY_ENV" = "prod" ] && ! git merge-base --is-ancestor HEAD origin/main; then
read -r -p "WARNING: $IMAGE_TAG is not on origin/main. Deploy it anyway? Type 'yes' to continue: " answer
[ "$answer" = "yes" ] || exit 1
fi
Expand Down Expand Up @@ -137,6 +161,13 @@ cd $TERRAFORM_DIR
# A saved plan contains secrets in plain text; always remove it
trap "rm -f '$PWD/tfplan'" EXIT
terraform init -reconfigure

# kubernetes_manifest resources need a reachable cluster at plan time, so a
# brand-new dev environment creates the cluster first in a separate apply.
if [ "$DEPLOY_ENV" = "dev" ] && ! gcloud container clusters describe "$CLUSTER_NAME" --zone "$ZONE" > /dev/null 2>&1; then
terraform apply -target=google_container_node_pool.primary_nodes
fi

terraform plan -out=tfplan

# The plan covers the database, networking and ingress as well as the API image,
Expand All @@ -154,6 +185,11 @@ cd - > /dev/null
echo "Connecting to GKE cluster..."
gcloud container clusters get-credentials $CLUSTER_NAME --zone "$ZONE"

# A new dev cluster has no Vertex key secret yet; create it from the local key file
if [ "$DEPLOY_ENV" = "dev" ] && ! kubectl get secret vertex-ai-credentials -n misinformation-mitigation > /dev/null 2>&1; then
kubectl -n misinformation-mitigation create secret generic vertex-ai-credentials --from-file=service-account.json
fi

kubectl rollout status deployment/misinformation-mitigation-api -n misinformation-mitigation --timeout=10m

echo "Checking API health at ${HEALTH_URL}..."
Expand Down
44 changes: 44 additions & 0 deletions infrastructure/terraform-dev/.terraform.lock.hcl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading