terraform/main.tf (272 lines of code) (raw):

# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. // import random string resource "random_string" "image_tag" { length = 16 special = false } locals { // generate a 16 digit hexadecimal string docker_image_tag = substr(md5(random_string.image_tag.result), 0, 16) frontend_bucket_name = "${var.project_id}-cymbal-frontend" frontend_static_ip = "${var.project_id}-cymbal-frontend-ip" inventory_image_url = "us-central1-docker.pkg.dev/${var.project_id}/cymbal-superstore/inventory-api:${local.docker_image_tag}" } // ------------- ENABLE APIS ----------------------------------------------------------------- resource "google_project_service" "cloudbuild" { project = var.project_id service = "cloudbuild.googleapis.com" disable_on_destroy = false disable_dependent_services = false } resource "google_project_service" "storage" { project = var.project_id service = "storage.googleapis.com" disable_on_destroy = false disable_dependent_services = false } resource "google_project_service" "run" { project = var.project_id service = "run.googleapis.com" disable_on_destroy = false disable_dependent_services = false } resource "google_project_service" "firestore" { project = var.project_id service = "firestore.googleapis.com" disable_on_destroy = false disable_dependent_services = false } resource "google_project_service" "spanner" { project = var.project_id service = "spanner.googleapis.com" disable_on_destroy = false disable_dependent_services = false } # // ------------- FIRESTORE - NATIVE MODE ----------------------------------- resource "google_firestore_database" "database" { project = var.project_id name = "(default)" location_id = "nam5" type = "FIRESTORE_NATIVE" depends_on = [google_project_service.firestore] } # // ------------- CLOUD STORAGE BUCKET ----------------------------------------------------------------- # // https://cloud.google.com/load-balancing/docs/https/setup-global-ext-https-buckets#terraform resource "google_storage_bucket" "frontend" { name = local.frontend_bucket_name location = var.region project = var.project_id website { main_page_suffix = "index.html" } } resource "google_storage_bucket_iam_member" "frontend" { bucket = google_storage_bucket.frontend.name role = "roles/storage.objectViewer" member = "allUsers" depends_on = [ google_storage_bucket.frontend ] } # // ------------- LOCAL EXEC - BUILD AND UPLOAD REACT FRONTEND TO CLOUD STORAGE ----------------------------------- resource "null_resource" "npm_build_frontend" { provisioner "local-exec" { command = "cd ../frontend && npm install && npm run build" } // always run triggers = { always_run = "${timestamp()}" } } resource "null_resource" "cloud_storage_upload_frontend" { provisioner "local-exec" { command = "gsutil -m cp -r ../frontend/build/* gs://${local.frontend_bucket_name}" } depends_on = [ null_resource.npm_build_frontend, google_storage_bucket.frontend ] // always run triggers = { always_run = "${timestamp()}" } } # // ------------- CLOUD LOAD BALANCER - REACT FRONTEND ----------------------------------- resource "google_compute_global_address" "frontend" { name = local.frontend_static_ip project = var.project_id depends_on = [google_storage_bucket.frontend] } resource "google_compute_backend_bucket" "frontend" { name = "cymbal-backend-bucket" bucket_name = google_storage_bucket.frontend.name project = var.project_id enable_cdn = true depends_on = [ google_compute_global_address.frontend ] } resource "google_compute_url_map" "frontend" { name = "cymbal-url-map" default_service = google_compute_backend_bucket.frontend.self_link project = var.project_id depends_on = [ google_compute_backend_bucket.frontend ] } resource "google_compute_target_http_proxy" "frontend" { name = "cymbal-http-proxy" url_map = google_compute_url_map.frontend.self_link project = var.project_id depends_on = [ google_compute_url_map.frontend ] } resource "google_compute_global_forwarding_rule" "frontend" { name = "http-lb-forwarding-rule" ip_protocol = "TCP" load_balancing_scheme = "EXTERNAL_MANAGED" port_range = "80" project = var.project_id target = google_compute_target_http_proxy.frontend.id ip_address = google_compute_global_address.frontend.address depends_on = [ google_compute_target_http_proxy.frontend ] } # // ------------- ARTIFACT REGISTRY --------------------------------------- // https://cloud.google.com/artifact-registry/docs/repositories/create-repos#docker_1 resource "google_artifact_registry_repository" "cymbal-superstore-artifact-repo-docker" { project = var.project_id location = "us-central1" repository_id = "cymbal-superstore" description = "cymbal superstore demo repository" format = "DOCKER" } # // ------------- LOCAL EXEC - BUILD AND PUSH INVENTORY API DOCKER IMAGE ------------------------------------- resource "null_resource" "docker_build_backend" { provisioner "local-exec" { command = "cd ../backend && docker build --platform linux/amd64 -t ${local.inventory_image_url} ." } // always run this on every terraform apply triggers = { always_run = timestamp() } } resource "null_resource" "docker_push_backend" { provisioner "local-exec" { command = "docker push ${local.inventory_image_url}" } triggers = { always_run = timestamp() } depends_on = [ null_resource.docker_build_backend, google_artifact_registry_repository.cymbal-superstore-artifact-repo-docker ] } # // ------------- CLOUD RUN SERVICE - INVENTORY API ----------------------------------------------------------------- data "google_iam_policy" "cloud_run_sa" { binding { role = "roles/datastore.user" members = [ "serviceAccount:${var.project_number}-compute@developer.gserviceaccount.com", ] } } resource "google_cloud_run_v2_service" "inventory" { name = "inventory" location = var.region project = var.project_id ingress = "INGRESS_TRAFFIC_ALL" template { containers { image = local.inventory_image_url env { name = "PROJECT_ID" value = var.project_id } ports { container_port = 8000 } startup_probe { initial_delay_seconds = 10 timeout_seconds = 10 period_seconds = 10 failure_threshold = 3 tcp_socket { port = 8000 } } liveness_probe { http_get { path = "/health" } initial_delay_seconds = 10 timeout_seconds = 10 period_seconds = 10 failure_threshold = 3 } } } depends_on = [null_resource.docker_push_backend, data.google_iam_policy.cloud_run_sa] } resource "google_cloud_run_v2_service_iam_member" "member" { location = google_cloud_run_v2_service.inventory.location project = google_cloud_run_v2_service.inventory.project name = google_cloud_run_v2_service.inventory.name role = "roles/run.invoker" member = "allUsers" } // ------------- BIGQUERY - SALES DATA ----------------------------------------------------------------- resource "google_bigquery_dataset" "bq_dataset" { project = var.project_id dataset_id = "cymbal_sales" friendly_name = "Cymbal Superstore Product Sales" description = "Product sales by week Q3 2023" location = "US" default_table_expiration_ms = 5184000000 } resource "google_bigquery_table" "bq_table" { project = var.project_id dataset_id = google_bigquery_dataset.bq_dataset.dataset_id table_id = "cymbalsalestable" depends_on = [ google_bigquery_dataset.bq_dataset ] } // upload sales_bq_rawdata to GCS bucket resource "google_storage_bucket_object" "bq_rawdata" { name = "sales_bq_rawdata.csv" bucket = google_storage_bucket.frontend.name source = "./sales_bq_rawdata.csv" depends_on = [ google_storage_bucket.frontend ] } // exec using the bq command to load sales_bq_rawdata.csv from GCS bucket "Frontend" into table resource "null_resource" "bq_load" { provisioner "local-exec" { command = "bq load --autodetect --source_format=CSV --skip_leading_rows=1 ${google_bigquery_dataset.bq_dataset.dataset_id}.${google_bigquery_table.bq_table.table_id} gs://${google_storage_bucket.frontend.name}/sales_bq_rawdata.csv" } depends_on = [ google_bigquery_table.bq_table, google_storage_bucket_object.bq_rawdata ] triggers = { always_run = timestamp() } } // SPANNER DATABASE resource "google_spanner_instance" "transactions" { project = var.project_id name = "cymbal-spanner-instance" config = "regional-us-central1" display_name = "Cymbal Superstore Transactions" num_nodes = 2 depends_on = [ google_project_service.spanner ] } resource "google_spanner_database" "database" { project = var.project_id instance = google_spanner_instance.transactions.name name = "transactions-db" version_retention_period = "7d" ddl = [ "CREATE TABLE t1 (t1 INT64 NOT NULL,) PRIMARY KEY(t1)", "CREATE TABLE t2 (t2 INT64 NOT NULL,) PRIMARY KEY(t2)", ] deletion_protection = false }