tools/quota-monitoring-alerting/java/terraform/main.tf (347 lines of code) (raw):
/*
Copyright 2022 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.
*/
provider "google" {
credentials = file("../CREDENTIALS_FILE.json")
project = var.project_id
region = var.region
}
# Enable Cloud Resource Manager API
module "project-service-cloudresourcemanager" {
source = "terraform-google-modules/project-factory/google//modules/project_services"
version = "4.0.0"
project_id = var.project_id
activate_apis = [
"cloudresourcemanager.googleapis.com"
]
}
# Enable APIs
module "project-services" {
source = "terraform-google-modules/project-factory/google//modules/project_services"
version = "4.0.0"
project_id = var.project_id
activate_apis = [
"compute.googleapis.com",
"iam.googleapis.com",
"monitoring.googleapis.com",
"storage.googleapis.com",
"storage-api.googleapis.com",
"bigquery.googleapis.com",
"pubsub.googleapis.com",
"appengine.googleapis.com",
"cloudscheduler.googleapis.com",
"cloudfunctions.googleapis.com",
"cloudbuild.googleapis.com",
"bigquerydatatransfer.googleapis.com"
]
depends_on = [module.project-service-cloudresourcemanager]
}
# Create Pub/Sub topic to list projects in the parent node
resource "google_pubsub_topic" "topic_alert_project_id" {
name = var.topic_alert_project_id
depends_on = [module.project-services]
}
# Create Pub/Sub topic to send notification
resource "google_pubsub_topic" "topic_alert_notification" {
name = var.topic_alert_notification
depends_on = [module.project-services]
}
# Cloud scheduler job to invoke cloud function
resource "google_cloud_scheduler_job" "job" {
name = var.scheduler_cron_job_name
description = var.scheduler_cron_job_description
schedule = var.scheduler_cron_job_frequency
time_zone = var.scheduler_cron_job_timezone
attempt_deadline = var.scheduler_cron_job_deadline
region = var.region
depends_on = [module.project-services]
retry_config {
retry_count = 1
}
http_target {
http_method = "POST"
uri = google_cloudfunctions_function.function-listProjects.https_trigger_url
body = base64encode("{\"organizations\":\"${var.organizations}\",\"threshold\":\"${var.threshold}\",\"projectId\":\"${var.project_id}\"}")
oidc_token {
service_account_email = var.service_account_email
}
}
}
# cloud function to list projects
resource "google_cloudfunctions_function" "function-listProjects" {
name = var.cloud_function_list_project
description = var.cloud_function_list_project_desc
runtime = "java11"
available_memory_mb = var.cloud_function_list_project_memory
source_archive_bucket = var.source_code_bucket_name
source_archive_object = var.source_code_zip
trigger_http = true
entry_point = "functions.ListProjects"
service_account_email = var.service_account_email
timeout = var.cloud_function_list_project_timeout
depends_on = [module.project-services]
environment_variables = {
PUBLISH_TOPIC = google_pubsub_topic.topic_alert_project_id.name
HOME_PROJECT = var.project_id
}
}
# IAM entry for all users to invoke the function
resource "google_cloudfunctions_function_iam_member" "invoker-listProjects" {
project = google_cloudfunctions_function.function-listProjects.project
region = google_cloudfunctions_function.function-listProjects.region
cloud_function = google_cloudfunctions_function.function-listProjects.name
depends_on = [module.project-services]
role = "roles/cloudfunctions.invoker"
member = "serviceAccount:${var.service_account_email}"
}
# Second cloud function to scan project
resource "google_cloudfunctions_function" "function-scanProject" {
name = var.cloud_function_scan_project
description = var.cloud_function_scan_project_desc
runtime = "java11"
available_memory_mb = var.cloud_function_scan_project_memory
source_archive_bucket = var.source_code_bucket_name
source_archive_object = var.source_code_zip
entry_point = "functions.ScanProjectQuotas"
service_account_email = var.service_account_email
timeout = var.cloud_function_scan_project_timeout
depends_on = [module.project-services]
event_trigger {
event_type = "google.pubsub.topic.publish"
resource = var.topic_alert_project_id
}
environment_variables = {
NOTIFICATION_TOPIC = google_pubsub_topic.topic_alert_notification.name
THRESHOLD = var.threshold
BIG_QUERY_DATASET = var.big_query_dataset_id
BIG_QUERY_TABLE = var.big_query_table_id
}
}
# IAM entry for all users to invoke the function
resource "google_cloudfunctions_function_iam_member" "invoker-scanProject" {
project = google_cloudfunctions_function.function-scanProject.project
region = google_cloudfunctions_function.function-scanProject.region
cloud_function = google_cloudfunctions_function.function-scanProject.name
depends_on = [module.project-services]
role = "roles/cloudfunctions.invoker"
member = "serviceAccount:${var.service_account_email}"
}
# Third cloud function to send notification
resource "google_cloudfunctions_function" "function-notificationProject" {
name = var.cloud_function_notification_project
description = var.cloud_function_notification_project_desc
runtime = "java11"
available_memory_mb = var.cloud_function_notification_project_memory
source_archive_bucket = var.source_code_bucket_name
source_archive_object = var.source_code_notification_zip
entry_point = "functions.SendNotification"
service_account_email = var.service_account_email
timeout = var.cloud_function_notification_project_timeout
depends_on = [module.project-services]
event_trigger {
event_type = "google.pubsub.topic.publish"
resource = var.topic_alert_notification
}
environment_variables = {
HOME_PROJECT = var.project_id
ALERT_DATASET = var.big_query_alert_dataset_id
ALERT_TABLE = var.big_query_alert_table_id
}
}
# IAM entry for all users to invoke the function
resource "google_cloudfunctions_function_iam_member" "invoker-notificationProject" {
project = google_cloudfunctions_function.function-notificationProject.project
region = google_cloudfunctions_function.function-notificationProject.region
cloud_function = google_cloudfunctions_function.function-notificationProject.name
depends_on = [module.project-services]
role = "roles/cloudfunctions.invoker"
member = "serviceAccount:${var.service_account_email}"
}
# BigQuery Dataset
resource "google_bigquery_dataset" "dataset" {
dataset_id = var.big_query_dataset_id
friendly_name = var.big_query_dataset_id
description = var.big_query_dataset_desc
location = var.big_query_dataset_location
default_partition_expiration_ms = var.big_query_dataset_default_partition_expiration_ms
depends_on = [module.project-services]
}
# BigQuery Table
resource "google_bigquery_table" "default" {
dataset_id = google_bigquery_dataset.dataset.dataset_id
table_id = var.big_query_table_id
time_partitioning {
type = var.big_query_table_partition
}
labels = {
env = "default"
}
schema = <<EOF
[
{
"name": "threshold",
"type": "INT64",
"mode": "NULLABLE",
"description": "region"
},
{
"name": "region",
"type": "STRING",
"mode": "NULLABLE",
"description": "region"
},
{
"name": "m_value",
"type": "STRING",
"mode": "NULLABLE",
"description": "Quota metric value - usage or limit"
},
{
"name": "mv_type",
"type": "STRING",
"mode": "NULLABLE",
"description": "Type of metric value - usage or limit"
},
{
"name": "vpc_name",
"type": "STRING",
"mode": "NULLABLE",
"description": "vpc name"
},
{
"name": "metric",
"type": "STRING",
"mode": "NULLABLE",
"description": "quota metric"
},
{
"name": "addedAt",
"type": "TIMESTAMP",
"mode": "NULLABLE",
"description": "timestamp"
},
{
"name": "project_id",
"type": "STRING",
"mode": "NULLABLE",
"description": "project id"
},
{
"name": "folder_id",
"type": "STRING",
"mode": "NULLABLE",
"description": "folder id"
},
{
"name": "targetpool_name",
"type": "STRING",
"mode": "NULLABLE",
"description": "target pool name"
},
{
"name": "org_id",
"type": "STRING",
"mode": "NULLABLE",
"description": "organization id of the project"
}
]
EOF
}
#Schedule Query to get Alerts data
resource "google_bigquery_data_transfer_config" "query_config" {
display_name = var.bigquery_data_transfer_query_name
location = var.big_query_dataset_location
data_source_id = "scheduled_query"
schedule = var.Alert_data_scanning_frequency
notification_pubsub_topic = "projects/${var.project_id}/topics/${var.topic_alert_notification}"
destination_dataset_id = google_bigquery_dataset.quota_usage_alert_dataset.dataset_id
depends_on = [module.project-services]
params = {
destination_table_name_template = var.big_query_alert_table_id
write_disposition = "WRITE_TRUNCATE"
query = "SELECT metric,usage,q_limit,consumption,project_id,region,HOUR AS addedAt FROM (SELECT project_id,region,metric,HOUR,q_limit,usage,ROUND((SAFE_DIVIDE(CAST(t.usage AS BIGNUMERIC),CAST(t.q_limit AS BIGNUMERIC))*100),2) AS consumption,threshold FROM (SELECT project_id,region,metric,DATE_TRUNC(addedAt, HOUR) AS HOUR,MAX(CASE WHEN mv_type='limit' THEN m_value ELSE NULL END) AS q_limit,MAX(CASE WHEN mv_type='usage' THEN m_value ELSE NULL END) AS usage,threshold FROM ${var.project_id}.${google_bigquery_dataset.dataset.dataset_id}.${google_bigquery_table.default.table_id} GROUP BY 1,2,3,4,7 ) t ) c WHERE c.consumption >= c.threshold"
}
}
#Bigquery Alert Dataset
resource "google_bigquery_dataset" "quota_usage_alert_dataset" {
dataset_id = var.big_query_alert_dataset_id
friendly_name = "quota_usage_alert_dataset"
description = var.big_query_alert_dataset_desc
location = var.big_query_dataset_location
depends_on = [module.project-services]
}
# Custom log-based metric to send quota alert data through
resource "google_logging_metric" "quota_logging_metric" {
name = "resource_usage"
description = "Tracks logs for quota usage above threshold"
filter = "logName:\"projects/${var.project_id}/logs/\" jsonPayload.message:\"|ProjectId | Scope |\""
depends_on = [module.project-services]
metric_descriptor {
metric_kind = "DELTA"
value_type = "INT64"
unit = "1"
labels {
key = "data"
value_type = "STRING"
}
}
label_extractors = {
"data" = "EXTRACT(jsonPayload.message)"
}
}
#Set notification channels below
#Add Notification channel - Email
resource "google_monitoring_notification_channel" "email0" {
display_name = "Oncall"
type = "email"
depends_on = [module.project-services]
labels = {
email_address = var.notification_email_address
}
}
#Add Notification channel - PagerDuty - supply service_key token
#resource "google_monitoring_notification_channel" "pagerDuty" {
# display_name = "PagerDuty Services"
# type = "pagerduty"
# labels = {
# "channel_name" = "#foobar"
# }
# sensitive_labels {
# service_key = "one"
# }
#}
#Email notification channel 1 output
output "email0_id" {
value = google_monitoring_notification_channel.email0.name
}
#Log sink to route logs to log bucket
resource "google_logging_project_sink" "instance-sink" {
name = var.log_sink_name
description = "Log sink to route logs sent by the Notification cloud function to the designated log bucket"
destination = "logging.googleapis.com/projects/${var.project_id}/locations/global/buckets/${var.alert_log_bucket_name}"
filter = "logName=\"projects/${var.project_id}/logs/quota-alerts\""
unique_writer_identity = true
depends_on = [module.project-services]
}
#Because our sink uses a unique_writer, we must grant that writer access to the bucket.
resource "google_project_iam_binding" "log-writer" {
project = var.project_id
role = "roles/logging.configWriter"
depends_on = [module.project-services]
members = [
"serviceAccount:${var.service_account_email}",
]
}
#Log bucket to store logs
resource "google_logging_project_bucket_config" "logging_bucket" {
project = var.project_id
location = "global"
retention_days = var.retention_days
bucket_id = var.alert_log_bucket_name
description = "Log bucket to store logs related to quota alerts sent by the Notification cloud function"
depends_on = [module.project-services]
}
#Alert policy for log-based metric
# Condition display name can be changed based on user's quota range
resource "google_monitoring_alert_policy" "alert_policy_quota" {
display_name = "Resources reaching Quotas"
combiner = "OR"
conditions {
display_name = "Resources reaching Quotas"
condition_threshold {
filter = "metric.type=\"logging.googleapis.com/user/${google_logging_metric.quota_logging_metric.name}\" AND resource.type=\"cloud_function\""
duration = "60s"
comparison = "COMPARISON_GT"
threshold_value = 0
trigger {
count = 1
}
aggregations {
per_series_aligner = "ALIGN_COUNT"
alignment_period = "60s"
}
}
}
documentation {
mime_type = "text/markdown"
content = "$${metric.label.data}"
}
notification_channels = [
google_monitoring_notification_channel.email0.name
]
depends_on = [module.project-services]
}