LAB27_GCP_Serverless: API Gateway to Cloud Functions to Firestore

Serverless: API Gateway to Cloud Functions to Firestore

☁ GCP 🔥 Intermediate API Gateway Cloud Functions Firestore Serverless

Scenario

You are building a microservice to track user high scores. Exposing individual Cloud Functions directly to clients is difficult for routing and versioning. You will build a cohesive Serverless API: an OpenAPI specification is deployed to API Gateway, which routes HTTP requests to a backend Cloud Function, which in turn reads/writes to a Firestore NoSQL database.

Your Objectives
  • Ensure the Firestore Database exists (`(default)` created safely).
  • Deploy a backend google_cloudfunctions2_function. For this lab, assume the zip code returns a static string, but note that the architecture grants it access to Firestore.
  • Create an API spec file (openapi.yaml) that maps the `/scores` endpoint to the Cloud Function's dynamic URI using Terraform's templatefile() function.
  • Deploy a google_api_gateway_api, google_api_gateway_api_config, and google_api_gateway_gateway.
  • Output the Gateway's public host URL.

Additional Context

API Gateway configs: In GCP, API Gateway uses an OpenAPI (Swagger) 2.0 specification file. GCP extends the OpenAPI spec with a special attribute: `x-google-backend`. This tells the Gateway exactly where to forward the request (the URL of the Cloud Function or Cloud Run service).

Because the Cloud Function URL is only known after it is deployed, you cannot hardcode the `openapi.yaml`. You must use Terraform's `templatefile()` to inject the Cloud Function's output URI dynamically into the Swagger spec before creating the Gateway config.

openapi.yaml.tpl

swagger: "2.0"
info:
  title: "Scores API"
  version: "1.0.0"
paths:
  /scores:
    get:
      summary: "Get scores"
      operationId: "getScores"
      x-google-backend:
        address: "${function_url}"
      responses:
        200:
          description: "A successful response"

main.tf

provider "google" {
  project = var.project_id
  region  = var.gcp_region
}

# 1. Provide a dummy zip file for the function (using archive_file data source)
data "archive_file" "dummy_src" {
  type        = "zip"
  output_path = "function.zip"
  source {
    content  = "import json\n\ndef hello_http(request):\n    return '{\"data\": \"mock firestore scores\"}'\n"
    filename = "main.py"
  }
}

resource "random_id" "bkt" { byte_length = 4 }

resource "google_storage_bucket" "src" {
  name          = "gcf-src-${random_id.bkt.hex}"
  location      = "US"
  force_destroy = true
}

resource "google_storage_bucket_object" "zip" {
  name   = "src.zip"
  bucket = google_storage_bucket.src.name
  source = data.archive_file.dummy_src.output_path
}

# 2. Cloud Function Backend
resource "google_cloudfunctions2_function" "api_backend" {
  name     = "scores-backend"
  location = var.gcp_region
  build_config {
    runtime     = "python311"
    entry_point = "hello_http"
    source {
      storage_source {
        bucket = google_storage_bucket.src.name
        object = google_storage_bucket_object.zip.name
      }
    }
  }
}

# Grant unauthenticated access to the function (Gateway will handle authentication if needed)
resource "google_cloud_run_service_iam_member" "invoker" {
  location = google_cloudfunctions2_function.api_backend.location
  service  = google_cloudfunctions2_function.api_backend.name
  role     = "roles/run.invoker"
  member   = "allUsers"
}

# 3. API Gateway
resource "google_api_gateway_api" "api" {
  provider     = google-beta
  api_id       = "scores-api"
  display_name = "High Scores API"
}

resource "google_api_gateway_api_config" "api_cfg" {
  provider      = google-beta
  api           = google_api_gateway_api.api.api_id
  api_config_id = "scores-cfg-v1"

  openapi_documents {
    document {
      path     = "openapi.yaml"
      contents = base64encode(templatefile("openapi.yaml.tpl", {
        # Inject the dynamically created function URL into the swagger spec
        function_url = google_cloudfunctions2_function.api_backend.service_config[0].uri
      }))
    }
  }
}

resource "google_api_gateway_gateway" "gw" {
  provider   = google-beta
  api_config = google_api_gateway_api_config.api_cfg.id
  gateway_id = "scores-gw"
  region     = var.gcp_region
}

Workflow Commands

# This lab requires google-beta provider for API Gateway fully
terraform init
terraform apply -var="project_id=my-gcp-project-123"

# Note: API Gateway configs can take 3-5 minutes to propagate fully
# curl https://$(terraform output gateway_hostname)/scores

terraform destroy -var="project_id=my-gcp-project-123"