tools/quota-monitoring-alerting/python/terraform/common/main.tf (363 lines of code) (raw):

# Copyright 2021 Google Inc. All Rights Reserved. # # 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 { timestamp = formatdate("YYYYMMDDhhmmss", timestamp()) config = yamldecode(file("${path.module}/../../config.yaml")) path = "${path.module}/../.." sa_org_roles = [ "roles/monitoring.admin", "roles/resourcemanager.folderViewer", "roles/viewer", ] } provider "google" { # Since this will be executed from cloud-shell for credentials use # gcloud auth application-default login project = var.project region = var.region } # API's and IAM #............................................................................... module "project_services" { source = "terraform-google-modules/project-factory/google//modules/project_services" project_id = var.project activate_apis = [ "bigquery.googleapis.com", "cloudbuild.googleapis.com", "cloudresourcemanager.googleapis.com", "cloudscheduler.googleapis.com", "iam.googleapis.com", "monitoring.googleapis.com", "run.googleapis.com" ] } resource "google_service_account" "quota_export_service_account" { account_id = var.name display_name = "Quota Export Service Account" depends_on = [module.project_services] } resource "google_service_account" "pubsub_invoker_service_account" { account_id = "cloud-run-pubsub-invoker" display_name = "Cloud Run Pub/Sub Invoker" depends_on = [module.project_services] } resource "google_service_account" "scheduler_invoker_service_account" { account_id = "cloud-run-scheduler-invoker" display_name = "Cloud Run Scheduler Invoker" depends_on = [module.project_services] } resource "null_resource" "service_accounts" { depends_on = [ module.project_services, resource.google_service_account.quota_export_service_account, resource.google_service_account.pubsub_invoker_service_account, resource.google_service_account.scheduler_invoker_service_account, ] } resource "google_project_iam_member" "quota_export_sa_role_1" { project = var.project role = "roles/iam.serviceAccountUser" member = "serviceAccount:${resource.google_service_account.quota_export_service_account.email}" depends_on = [resource.null_resource.service_accounts] } resource "google_project_iam_member" "quota_export_sa_role_2" { project = var.project role = "roles/bigquery.admin" member = "serviceAccount:${resource.google_service_account.quota_export_service_account.email}" depends_on = [resource.null_resource.service_accounts] } resource "google_project_iam_member" "quota_export_sa_role_3" { project = var.project role = "roles/pubsub.admin" member = "serviceAccount:${resource.google_service_account.quota_export_service_account.email}" depends_on = [resource.null_resource.service_accounts] } resource "google_project_iam_member" "quota_export_sa_role_4" { project = var.project role = "roles/run.serviceAgent" member = "serviceAccount:service-${var.project_number}@serverless-robot-prod.iam.gserviceaccount.com" depends_on = [resource.null_resource.service_accounts] } resource "google_project_iam_member" "quota_export_sa_role_5" { project = var.project role = "roles/cloudbuild.serviceAgent" member = "serviceAccount:${var.project_number}@cloudbuild.gserviceaccount.com" depends_on = [resource.null_resource.service_accounts] } resource "google_project_iam_member" "quota_export_sa_role_6" { project = var.project role = "roles/cloudscheduler.serviceAgent" member = "serviceAccount:service-${var.project_number}@gcp-sa-cloudscheduler.iam.gserviceaccount.com" depends_on = [resource.null_resource.service_accounts] } resource "google_project_iam_member" "quota_export_sa_role_7" { project = var.project role = "roles/iam.serviceAccountTokenCreator" member = "serviceAccount:service-${var.project_number}@gcp-sa-pubsub.iam.gserviceaccount.com" depends_on = [resource.null_resource.service_accounts] } resource "google_project_iam_member" "quota_export_sa_role_8" { project = var.project role = "roles/run.invoker" member = "serviceAccount:${resource.google_service_account.pubsub_invoker_service_account.email}" depends_on = [resource.null_resource.service_accounts] } resource "google_project_iam_member" "quota_export_sa_role_9" { project = var.project role = "roles/run.invoker" member = "serviceAccount:${resource.google_service_account.scheduler_invoker_service_account.email}" depends_on = [resource.null_resource.service_accounts] } resource "null_resource" "project_iam" { depends_on = [ resource.google_project_iam_member.quota_export_sa_role_1, resource.google_project_iam_member.quota_export_sa_role_2, resource.google_project_iam_member.quota_export_sa_role_3, resource.google_project_iam_member.quota_export_sa_role_4, resource.google_project_iam_member.quota_export_sa_role_5, resource.google_project_iam_member.quota_export_sa_role_6, resource.google_project_iam_member.quota_export_sa_role_7, resource.google_project_iam_member.quota_export_sa_role_8, resource.google_project_iam_member.quota_export_sa_role_9, ] } resource "google_organization_iam_member" "org_iam" { count = length(local.sa_org_roles) org_id = var.org role = local.sa_org_roles[count.index] member = "serviceAccount:${resource.google_service_account.quota_export_service_account.email}" depends_on = [module.project_services, resource.null_resource.service_accounts] } # Local-exec #............................................................................... resource "null_resource" "replace_project_id_in_config" { provisioner "local-exec" { command = "sed -i 's~$PROJECT~'$PROJECT'~g' ${local.path}/config.yaml" environment = { PROJECT = var.project } } } # Bigquery #............................................................................... resource "google_bigquery_dataset" "quota" { dataset_id = local.config.export["bigquery"]["dataset"] depends_on = [module.project_services, resource.google_organization_iam_member.org_iam, resource.null_resource.project_iam, ] } resource "google_bigquery_table" "metrics" { dataset_id = google_bigquery_dataset.quota.dataset_id table_id = "metrics" time_partitioning { type = "DAY" } schema = file("${local.path}/bigquery_schemas/metrics") depends_on = [resource.google_bigquery_dataset.quota] deletion_protection = false } resource "google_bigquery_table" "thresholds" { dataset_id = google_bigquery_dataset.quota.dataset_id table_id = "thresholds" time_partitioning { type = "DAY" } schema = file("${local.path}/bigquery_schemas/thresholds") depends_on = [resource.google_bigquery_dataset.quota] deletion_protection = false } resource "null_resource" "replace_project_id" { provisioner "local-exec" { command = "sed 's~$PROJECT~'$PROJECT'~g' ${local.path}/bigquery_schemas/dashboard_view.sql > ${local.path}/templates/outputs/dashboard_view.sql" environment = { PROJECT = var.project } } } data "local_file" "dashboard_view_sql" { filename = "${local.path}/templates/outputs/dashboard_view.sql" depends_on = [resource.null_resource.replace_project_id] } resource "google_bigquery_table" "dashboard_view" { dataset_id = local.config.export["bigquery"]["dataset"] table_id = "dashboard_view" view { query = data.local_file.dashboard_view_sql.content use_legacy_sql = "false" } depends_on = [resource.google_bigquery_dataset.quota, resource.google_bigquery_table.metrics, resource.google_bigquery_table.thresholds, ] deletion_protection = false } # Cloud Run #............................................................................... resource "null_resource" "build" { triggers = { always_run = local.timestamp } provisioner "local-exec" { command =<<-EOT gcloud beta builds submit ${local.path} \ --config ${local.path}/cloudbuild.yaml \ --substitutions=_SERVICE=$SERVICE,_PROJECT=$PROJECT,_TAG=$TAG EOT environment = { PROJECT = var.project SERVICE = var.name TAG = local.timestamp } } depends_on = [module.project_services, resource.google_organization_iam_member.org_iam, resource.null_resource.project_iam, ] } data "google_container_registry_image" "qms_image_latest" { name = var.name project = var.project tag = local.timestamp depends_on = [resource.null_resource.build] } resource "google_cloud_run_service" "crun" { name = var.name location = var.region template { spec { containers { image = data.google_container_registry_image.qms_image_latest.image_url } container_concurrency = 5 service_account_name = "${var.name}@${var.project}.iam.gserviceaccount.com" } } traffic { percent = 100 latest_revision = true } } # PubSub #............................................................................... resource "null_resource" "pubsub" { depends_on = [module.project_services, resource.google_organization_iam_member.org_iam, resource.null_resource.project_iam] } resource "google_pubsub_topic" "dead_letter" { name = "dead-letter" depends_on = [resource.null_resource.pubsub] } resource "google_pubsub_topic" "metrics" { name = "metrics" depends_on = [resource.null_resource.pubsub] } resource "google_pubsub_topic" "thresholds" { name = "thresholds" depends_on = [resource.null_resource.pubsub] } resource "google_pubsub_topic" "bigquery" { name = "bigquery" depends_on = [resource.null_resource.pubsub] } resource "google_pubsub_subscription" "metrics_sub" { name = "metrics_sub" topic = resource.google_pubsub_topic.metrics.name ack_deadline_seconds = 60 message_retention_duration = "900s" push_config { push_endpoint = "${resource.google_cloud_run_service.crun.status[0].url}/project/metric/list" oidc_token { service_account_email = resource.google_service_account.pubsub_invoker_service_account.email } } retry_policy { minimum_backoff = "60s" } dead_letter_policy { dead_letter_topic = resource.google_pubsub_topic.dead_letter.id max_delivery_attempts = 5 } depends_on = [resource.null_resource.pubsub] } resource "google_pubsub_subscription" "thresholds_sub" { name = "thresholds_sub" topic = resource.google_pubsub_topic.thresholds.name ack_deadline_seconds = 60 message_retention_duration = "900s" push_config { push_endpoint = "${resource.google_cloud_run_service.crun.status[0].url}/project/metric/threshold/list" oidc_token { service_account_email = resource.google_service_account.pubsub_invoker_service_account.email } } retry_policy { minimum_backoff = "60s" } dead_letter_policy { dead_letter_topic = resource.google_pubsub_topic.dead_letter.id max_delivery_attempts = 5 } depends_on = [resource.null_resource.pubsub] } resource "google_pubsub_subscription" "bigquery_sub" { name = "bigquery_sub" topic = resource.google_pubsub_topic.bigquery.name ack_deadline_seconds = 60 message_retention_duration = "900s" push_config { push_endpoint = "${resource.google_cloud_run_service.crun.status[0].url}/project/metric/save" oidc_token { service_account_email = resource.google_service_account.pubsub_invoker_service_account.email } } retry_policy { minimum_backoff = "60s" } dead_letter_policy { dead_letter_topic = resource.google_pubsub_topic.dead_letter.id max_delivery_attempts = 5 } depends_on = [resource.null_resource.pubsub] } # Scheduler #............................................................................... resource "null_resource" "scheduler" { depends_on = [module.project_services, resource.google_organization_iam_member.org_iam, resource.null_resource.project_iam, ] } resource "google_cloud_scheduler_job" "quota_export_job" { name = "${var.name}-job" description = "Job to trigger Quota export to BigQuery" schedule = "0 */12 * * *" attempt_deadline = "320s" retry_config { retry_count = 1 } http_target { http_method = "GET" uri = "${resource.google_cloud_run_service.crun.status[0].url}/project/list" oidc_token { service_account_email = resource.google_service_account.scheduler_invoker_service_account.email audience = "${resource.google_cloud_run_service.crun.status[0].url}/project/list" } } depends_on = [resource.null_resource.scheduler] } resource "google_cloud_scheduler_job" "quota_export_thresholds_job" { name = "${var.name}-thresholds-job" description = "Job to trigger Quota thresholds check" schedule = "30 */12 * * *" attempt_deadline = "320s" retry_config { retry_count = 1 } http_target { http_method = "GET" uri = "${resource.google_cloud_run_service.crun.status[0].url}/report/thresholds" oidc_token { service_account_email = resource.google_service_account.scheduler_invoker_service_account.email audience = "${resource.google_cloud_run_service.crun.status[0].url}/report/thresholds" } } depends_on = [resource.null_resource.scheduler] } # Outputs #............................................................................... output "copy_dashboard_url" { value = join("", [ "https://datastudio.google.com/reporting/create?", "c.reportId=50bdadac-9ea0-4dcd-bee2-f323c968186d&r.reportName=Copy-QMS", "&ds.ds01.connector=BigQuery&ds.ds01.projectId=${var.project}", "&ds.ds01.type=TABLE&ds.ds01.datasetId=quota&ds.ds01.tableId=", "${resource.google_bigquery_table.thresholds.table_id}", "&ds.ds02.connector=BigQuery&ds.ds02.projectId=${var.project}", "&ds.ds02.type=TABLE&ds.ds02.datasetId=quota&ds.ds02.tableId=", "${resource.google_bigquery_table.dashboard_view.table_id}" ]) }