udf/terraform/main.tf (132 lines of code) (raw):
#
# Copyright 2024 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
#
# https://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 configuration
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.0.0"
}
}
}
provider "google" {
project = var.project_id
region = var.region
}
# Artifact Registry repository for the Docker image
resource "google_artifact_registry_repository" "image_registry" {
location = var.region
repository_id = var.artifact_registry_name
description = "Docker repository for BigQuery anti-pattern function"
format = "DOCKER"
}
resource "google_service_account" "cloud_build_sa" {
account_id = "cloud-build-sa"
display_name = "Cloud Build Service Account"
}
resource "google_project_iam_member" "log_writer_role" {
depends_on = [google_service_account.cloud_build_sa]
project = var.project_id
role = "roles/logging.logWriter"
member = "serviceAccount:${google_service_account.cloud_build_sa.email}"
}
resource "google_project_iam_member" "object_reader_role" {
depends_on = [google_service_account.cloud_build_sa]
project = var.project_id
role = "roles/storage.objectViewer"
member = "serviceAccount:${google_service_account.cloud_build_sa.email}"
}
resource "google_artifact_registry_repository_iam_member" "artifact_registry_writer" {
depends_on = [google_artifact_registry_repository.image_registry]
project = var.project_id
location = var.region
repository = var.artifact_registry_name
role = "roles/artifactregistry.writer"
member = "serviceAccount:${google_service_account.cloud_build_sa.email}"
}
# Build image with `../../cloudbuild-udf.yaml`, uses AntiPatternApplication.java as main class
resource "null_resource" "build_function_image" {
depends_on = [google_artifact_registry_repository.image_registry, google_project_iam_member.object_reader_role, google_project_iam_member.log_writer_role, google_artifact_registry_repository_iam_member.artifact_registry_writer ]
triggers = {
project_id = var.project_id
region = var.region
full_sa_path = "projects/${var.project_id}/serviceAccounts/${google_service_account.cloud_build_sa.email}"
full_image_path = "${var.region}-docker.pkg.dev/${var.project_id}/${google_artifact_registry_repository.image_registry.name}/${var.service_name}:latest"
}
provisioner "local-exec" {
when = create
command = <<EOF
#!/bin/bash
set -e # Exit immediately on any error
cd ../../
max_retries=3
retry_delay=60 # Seconds
for i in $(seq 1 $max_retries); do
if gcloud builds submit \
--project ${var.project_id} \
--region ${var.region} \
--machine-type=e2-highcpu-8 \
--service-account=${self.triggers.full_sa_path} \
--config=cloudbuild-udf.yaml \
--substitutions=_CONTAINER_IMAGE_NAME=${self.triggers.full_image_path}; then
echo "Build submitted successfully."
break # Exit the loop if successful
else
if [ $i -eq $max_retries ]; then
echo "Max retries reached. Build failed."
exit 1 # Fail the Terraform resource
else
echo "Build failed. Retrying in $retry_delay seconds..."
sleep $retry_delay
fi
fi
done
EOF
}
}
# Cloud Run service to host the BigQuery remote function
resource "google_cloud_run_service" "antipattern_service" {
name = var.service_name
project = var.project_id
location = var.region
depends_on = [null_resource.build_function_image]
template {
spec {
containers {
image = null_resource.build_function_image.triggers.full_image_path
}
}
}
}
# BigQuery connection to the Cloud Run service
resource "google_bigquery_connection" "external_bq_fn_connection" {
project = var.project_id
connection_id = "ext-${var.service_name}"
location = var.region
description = "External Antipattern function connection"
cloud_resource {}
}
resource "google_project_iam_binding" "grant_bq_connection_run_invoker_role" {
project = var.project_id
role = "roles/run.invoker"
members = [
"serviceAccount:${google_bigquery_connection.external_bq_fn_connection.cloud_resource[0].service_account_id}"
]
}
resource "google_bigquery_dataset" "routines_dataset" {
project = var.project_id
location = var.region
dataset_id = var.bq_dataset
}
# Creates remote function
resource "null_resource" "antipattern_function" {
depends_on = [google_cloud_run_service.antipattern_service, google_bigquery_connection.external_bq_fn_connection, google_bigquery_dataset.routines_dataset]
triggers = {
project_id = var.project_id
region = var.region
dataset_id = var.bq_dataset
cloud_service_name = google_cloud_run_service.antipattern_service.id
cloud_run_uri = google_cloud_run_service.antipattern_service.status[0].url
}
provisioner "local-exec" {
when = create
command = <<EOF
bq query --project_id "${self.triggers.project_id}" \
--use_legacy_sql=false \
"CREATE OR REPLACE FUNCTION ${self.triggers.dataset_id}.get_antipatterns(query STRING) RETURNS JSON \
REMOTE WITH CONNECTION \`${self.triggers.project_id}.${self.triggers.region}.${google_bigquery_connection.external_bq_fn_connection.connection_id}\` \
OPTIONS (endpoint = '${self.triggers.cloud_run_uri}');" \
EOF
}
}