main.tf (237 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
#
# 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.
#
######################################
## Initializing Cloud Services ##
######################################
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.0.0"
}
}
provider_meta "google" {
module_name = "cloud-solutions/deploy-bigquery-dlp-remote-function-v0.1"
}
}
provider "google" {
billing_project = var.project_id
project = var.project_id
region = var.region
}
###################################
## Creating Cloud Resources ##
###################################
resource "google_service_account" "run_service_account" {
account_id = "${var.service_name}-runner"
project = var.project_id
}
resource "google_project_iam_member" "grant_role_to_sa" {
for_each = toset([
"roles/dlp.reader",
"roles/dlp.user",
])
project = var.project_id
role = each.key
member = "serviceAccount:${google_service_account.run_service_account.email}"
}
resource "google_artifact_registry_repository" "image_registry" {
format = "DOCKER"
repository_id = var.artifact_registry_name
project = var.project_id
location = var.region
}
resource "google_service_account" "build_service_account" {
account_id = "${var.service_name}-builder"
project = var.project_id
}
resource "google_project_iam_member" "grant_role_to_build_sa" {
project = var.project_id
role = "roles/cloudbuild.builds.builder"
member = "serviceAccount:${google_service_account.build_service_account.email}"
}
resource "google_storage_bucket" "cloud_build_bucket" {
project = var.project_id
location = var.region
name = "build_bucket_${var.service_name}"
uniform_bucket_level_access = true
public_access_prevention = "enforced"
force_destroy = true
}
resource "google_storage_bucket_iam_member" "builder_iam_bucket" {
for_each = toset([
"roles/cloudbuild.builds.builder"
])
bucket = google_storage_bucket.cloud_build_bucket.name
member = "serviceAccount:${google_service_account.build_service_account.email}"
role = each.key
}
## Create Image using Cloud Build and store in artifact registry
resource "random_id" "build_version" {
byte_length = 8
keepers = {
project_id = var.project_id
region = var.region
}
}
resource "null_resource" "build_function_image" {
depends_on = [
google_artifact_registry_repository.image_registry,
google_storage_bucket_iam_member.builder_iam_bucket
]
triggers = {
project_id = var.project_id
region = var.region
full_image_path = "${var.region}-docker.pkg.dev/${var.project_id}/${google_artifact_registry_repository.image_registry.name}/${var.service_name}:${random_id.build_version.hex}"
}
provisioner "local-exec" {
when = create
command = <<EOF
gcloud builds submit \
--project ${var.project_id} \
--region ${var.region} \
--machine-type e2-highcpu-8 \
--substitutions _CONTAINER_IMAGE_NAME=${self.triggers.full_image_path} \
--service-account ${google_service_account.build_service_account.id} \
--default-buckets-behavior regional-user-owned-bucket \
--gcs-source-staging-dir gs://${google_storage_bucket.cloud_build_bucket.name}/source
EOF
}
provisioner "local-exec" {
when = destroy
command = <<EOF
gcloud artifacts docker images delete \
${self.triggers.full_image_path} \
--quiet
EOF
}
}
resource "google_cloud_run_v2_service" "bq_function" {
location = var.region
name = var.service_name
project = var.project_id
depends_on = [null_resource.build_function_image]
deletion_protection = false
template {
service_account = google_service_account.run_service_account.email
execution_environment = "EXECUTION_ENVIRONMENT_GEN2"
containers {
image = null_resource.build_function_image.triggers.full_image_path
env {
name = "PROJECT_ID"
value = var.project_id
}
}
}
}
resource "google_bigquery_connection" "external_bq_fn_connection" {
project = var.project_id
connection_id = "ext-${var.service_name}"
location = var.region
description = "External transformation 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
}
## Create DLP DeId Template
resource "random_id" "random_de_id_template_id_random" {
byte_length = 8
prefix = "bqdlpfn_"
keepers = {
project_id = var.project_id
region = var.region
}
}
locals {
template_id = random_id.random_de_id_template_id_random.hex
de_identify_template_json = merge(jsondecode(file(var.dlp_deid_template_json_file)), { templateId = local.template_id })
}
resource "null_resource" "dlp_de_identify_template" {
triggers = {
project_id = var.project_id
region = var.region
dlp_de_id_template_id = local.template_id
dlp_de_id_template_full_path = "projects/${var.project_id}/locations/${var.region}/deidentifyTemplates/${local.template_id}"
}
provisioner "local-exec" {
when = create
command = <<EOF
curl -s https://dlp.googleapis.com/v2/projects/${self.triggers.project_id}/locations/${self.triggers.region}/deidentifyTemplates \
--header "X-Goog-User-Project: ${var.project_id}" \
--header "Authorization: Bearer $(gcloud auth print-access-token)" \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data '${jsonencode(local.de_identify_template_json)}'
EOF
}
provisioner "local-exec" {
when = destroy
command = <<EOF
curl -s --request DELETE \
https://dlp.googleapis.com/v2/${self.triggers.dlp_de_id_template_full_path} \
--header "X-Goog-User-Project: ${self.triggers.project_id}" \
--header "Authorization: Bearer $(gcloud auth print-access-token)" \
--header 'Accept: application/json' \
--header "Content-Type: application/json"
EOF
}
}
## Create BigQuery remote functions
resource "random_id" "bq_job_random" {
byte_length = 8
}
resource "null_resource" "bq_dlp_encrypt_function" {
depends_on = [null_resource.dlp_de_identify_template, google_cloud_run_v2_service.bq_function, 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_v2_service.bq_function.id
cloud_run_uri = google_cloud_run_v2_service.bq_function.uri
}
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}.dlp_freetext_encrypt(v STRING) RETURNS STRING \
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}', user_defined_context = [('mode', 'deidentify'),('algo','dlp'),('dlp-deid-template','${null_resource.dlp_de_identify_template.triggers.dlp_de_id_template_full_path}'),('dlp-inspect-template','${var.dlp_inspect_template_full_path}')]);" \
EOF
}
provisioner "local-exec" {
when = destroy
command = <<EOF
bq query --project_id "${self.triggers.project_id}" \
--use_legacy_sql=false \
"DROP FUNCTION ${self.triggers.dataset_id}.dlp_freetext_encrypt" \
EOF
}
}
resource "null_resource" "bq_dlp_decrypt_function" {
depends_on = [null_resource.dlp_de_identify_template, google_cloud_run_v2_service.bq_function, 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_v2_service.bq_function.id
cloud_run_uri = google_cloud_run_v2_service.bq_function.uri
}
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}.dlp_freetext_decrypt(v STRING) RETURNS STRING \
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}', user_defined_context = [('mode', 'reidentify'),('algo','dlp'),('dlp-deid-template','${null_resource.dlp_de_identify_template.triggers.dlp_de_id_template_full_path}'),('dlp-inspect-template','${var.dlp_inspect_template_full_path}')]);" \
EOF
}
provisioner "local-exec" {
when = destroy
command = <<EOF
bq query --project_id "${self.triggers.project_id}" \
--use_legacy_sql=false \
"DROP FUNCTION ${self.triggers.dataset_id}.dlp_freetext_decrypt" \
EOF
}
}