infra/postdeployment.tf (188 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. */ locals { random_suffix_value = var.random_suffix ? random_id.suffix.hex : "" # literal value (NNNN) random_suffix_append = var.random_suffix ? "-${random_id.suffix.hex}" : "" # appended value (-NNNN) setup_job_name = "setup${local.random_suffix_append}" client_job_name = "client${local.random_suffix_append}" gcloud_step_container = "gcr.io/google.com/cloudsdktool/cloud-sdk:slim" } # used to collect access token, for authenticated POST commands data "google_client_config" "current" { } # topic that is never used, except as a configuration type for Cloud Build Triggers resource "google_pubsub_topic" "faux" { name = "faux-topic${local.random_suffix_append}" } ## Failing Build - help get Cloud Build ready for work resource "google_cloudbuild_trigger" "activategcb" { count = var.init ? 1 : 0 name = "activategcb${local.random_suffix_append}" location = var.region description = "Get Cloud Build ready for work. This build is expected to fail." pubsub_config { topic = google_pubsub_topic.faux.id } service_account = google_service_account.init[0].id build { step { id = "no-op" name = "ubuntu" script = "echo 'Hello Cloud Build'" } options { logging = "CLOUD_LOGGING_ONLY" } } depends_on = [ # This is waiting for IAM to update but skipping the delay for IAM propagation. google_project_iam_member.init_permissions ] } # execute the trigger, once it and other dependencies exist. Intended side-effect. # tflint-ignore: terraform_unused_declarations data "http" "execute_activategcb_trigger" { count = var.init ? 1 : 0 url = "https://cloudbuild.googleapis.com/v1/${google_cloudbuild_trigger.activategcb[0].id}:run" method = "POST" request_headers = { Authorization = "Bearer ${data.google_client_config.current.access_token}" } depends_on = [ google_cloudbuild_trigger.activategcb[0] ] } ## Placeholder - deploys a placeholder website - uses prebuilt image in /app/placeholder resource "google_cloudbuild_trigger" "placeholder" { count = var.init ? 1 : 0 name = "placeholder${local.random_suffix_append}" location = var.region description = "Deploy a placeholder Firebase website" pubsub_config { topic = google_pubsub_topic.faux.id } service_account = google_service_account.init[0].id build { step { id = "deploy-placeholder" name = local.placeholder_image env = [ "PROJECT_ID=${var.project_id}", "SUFFIX=${local.random_suffix_value}", "FIREBASE_URL=${local.firebase_url}", ] } options { logging = "CLOUD_LOGGING_ONLY" } } depends_on = [ time_sleep.init_permissions_propagation ] } # execute the trigger, once it and other dependencies exist. Intended side-effect. # tflint-ignore: terraform_unused_declarations data "http" "execute_placeholder_trigger" { count = var.init ? 1 : 0 url = "https://cloudbuild.googleapis.com/v1/${google_cloudbuild_trigger.placeholder[0].id}:run" method = "POST" request_headers = { Authorization = "Bearer ${data.google_client_config.current.access_token}" } depends_on = [ google_cloudbuild_trigger.placeholder[0], # Do not trigger placeholder build before activategcb build. # activategcb exists to be run first. data.http.execute_activategcb_trigger[0] ] } ## Initalization trigger resource "google_cloudbuild_trigger" "init" { count = var.init ? 1 : 0 name = "init-application${local.random_suffix_append}" location = var.region description = "Perform initialization setup for server and client" pubsub_config { topic = google_pubsub_topic.faux.id } service_account = google_service_account.init[0].id build { ## Client/frontend processing step { # Check if a job already exists under the exact name. If it doesn't, create it. # Environment variables used to customise Firebase configuration on deployment # https://github.com/GoogleCloudPlatform/avocano/blob/main/client/docker-deploy.sh id = "create-client-job" name = local.gcloud_step_container script = <<EOT #!/bin/bash SETUP_JOB=$(gcloud run jobs list --filter "metadata.name~${local.client_job_name}$" --format "value(metadata.name)" --region ${var.region}) if [[ -z $SETUP_JOB ]]; then echo "Creating ${local.client_job_name} Cloud Run Job" gcloud run jobs create ${local.client_job_name} --region ${var.region} \ --image ${local.client_image} \ --service-account ${google_service_account.client.email} \ --set-env-vars PROJECT_ID=${var.project_id} \ --set-env-vars SUFFIX=${local.random_suffix_value} \ --set-env-vars REGION=${var.region} \ --set-env-vars SERVICE_NAME=${google_cloud_run_v2_service.server.name} else echo "Cloud Run Job ${local.client_job_name} already exists." fi EOT } ## Server/API processing step { # Check if a job already exists under the exact name. If it doesn't, create it. id = "create-setup-job" name = local.gcloud_step_container script = <<EOT #!/bin/bash SETUP_JOB=$(gcloud run jobs list --filter "metadata.name~${local.setup_job_name}$" --format "value(metadata.name)" --region ${var.region}) if [[ -z $SETUP_JOB ]]; then echo "Creating ${local.setup_job_name} Cloud Run Job" gcloud run jobs create ${local.setup_job_name} --region ${var.region} \ --command setup \ --image ${local.server_image} \ --service-account ${google_service_account.automation.email} \ --set-secrets DJANGO_ENV=${google_secret_manager_secret.django_settings.secret_id}:latest \ --set-secrets DJANGO_SUPERUSER_PASSWORD=${google_secret_manager_secret.django_admin_password.secret_id}:latest \ --set-cloudsql-instances ${google_sql_database_instance.postgres.connection_name} else echo "Cloud Run Job ${local.setup_job_name} already exists." fi EOT } # Now that the jobs definitely exist, execute them. step { id = "execute-setup-job" name = local.gcloud_step_container script = "gcloud run jobs execute ${local.setup_job_name} --wait --region ${var.region} --project ${var.project_id}" } step { id = "execute-client-job" name = local.gcloud_step_container script = "gcloud run jobs execute ${local.client_job_name} --wait --region ${var.region} --project ${var.project_id}" } # Ensure any cached versions of the application are purged # Preemptively warm up the server API step { id = "purge-and-warmfirebase" name = "ubuntu" script = <<EOT #!/bin/bash apt-get update && apt-get install curl -y curl -X PURGE "${local.firebase_url}/" curl "${google_cloud_run_v2_service.server.uri}/api/products/?warmup" EOT } options { logging = "CLOUD_LOGGING_ONLY" } } depends_on = [ google_sql_database_instance.postgres, google_cloud_run_v2_job.migrate, # Expected to be shorter than database provisioning. time_sleep.init_permissions_propagation ] } # execute the trigger, once it and other dependencies exist. Intended side-effect. # tflint-ignore: terraform_unused_declarations data "http" "execute_init_trigger" { count = var.init ? 1 : 0 url = "https://cloudbuild.googleapis.com/v1/${google_cloudbuild_trigger.init[0].id}:run" method = "POST" request_headers = { Authorization = "Bearer ${data.google_client_config.current.access_token}" } depends_on = [ google_cloudbuild_trigger.init[0], ] }