terraform/main.tf (224 lines of code) (raw):
/**
* Copyright 2025 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.
*/
data "google_project" "project" {
project_id = var.project
}
# ------------------------------------------------------
# Cloud Workflows deployment
# ------------------------------------------------------
resource "local_file" "parameters_file" {
filename = "../workflow-definitions/platform-parameters-${var.environment}.json"
content = jsonencode(local.workflows_generator_params)
}
resource "null_resource" "deploy_cloud_workflows" {
for_each = var.deploy_cloud_workflows ? local.cloud_workflows_filenames : []
provisioner "local-exec" {
command = <<EOF
python3 ../workflows-generator/orchestration_generator.py \
../workflow-definitions/${each.value} \
../workflow-definitions/platform-parameters-${var.environment}.json \
../cloud-workflows/${each.value} \
False
EOF
}
triggers = {
always_run = timestamp()
}
depends_on = [local_file.parameters_file]
}
resource "google_workflows_workflow" "workflows" {
for_each = var.deploy_cloud_workflows ? local.cloud_workflows_filenames : []
name = replace(each.value, ".json", "")
region = var.region
project = var.project
call_log_level = var.workflows_log_level
source_contents = data.local_file.workflow[each.value].content
deletion_protection = false
depends_on = [data.local_file.workflow]
}
data "local_file" "workflow" {
for_each = var.deploy_cloud_workflows ? local.cloud_workflows_filenames : []
filename = "../cloud-workflows/${each.value}"
depends_on = [null_resource.deploy_cloud_workflows]
}
# ------------------------------------------------------
# Cloud Composer deployment
# ------------------------------------------------------
resource "null_resource" "deploy_composer_dags" {
for_each = var.deploy_composer_dags ? local.composer_filenames : []
provisioner "local-exec" {
command = <<EOF
python3 ../workflows-generator/orchestration_generator.py \
../workflow-definitions/${each.value} \
../workflow-definitions/platform-parameters-${var.environment}.json \
../composer-dags/${replace(each.value, ".json", ".py")} \
False
EOF
}
triggers = {
always_run = timestamp()
}
depends_on = [local_file.parameters_file]
}
resource "google_storage_bucket_object" "uploaded_artifacts_aef_composer" {
for_each = var.deploy_composer_dags && var.composer_bucket_name == null ? fileset("../composer-dags/", "**/*") : []
name = "dags/${each.key}"
bucket = "${replace(replace(google_composer_environment.aef_composer_environment[0].config[0].dag_gcs_prefix, "gs://", ""),"/dags","")}"
source = "../composer-dags/${each.key}"
depends_on = [google_composer_environment.aef_composer_environment, null_resource.deploy_composer_dags]
}
resource "google_storage_bucket_object" "uploaded_artifacts_external_composer" {
for_each = var.deploy_composer_dags && var.composer_bucket_name != null ? fileset("../composer-dags/", "**/*") : []
name = "dags/${each.key}"
bucket = "gs://${var.composer_bucket_name}"
source = "../composer-dags/${each.key}"
depends_on = [null_resource.deploy_composer_dags]
}
module "vpc" {
source = "github.com/GoogleCloudPlatform/cloud-foundation-fabric/modules/net-vpc"
project_id = var.project
name = "psoaef-composer-vpc"
subnets = [
{
name = "psoaef-composer-${var.region}"
region = var.region
ip_cidr_range = "172.20.0.0/16"
secondary_ip_ranges = {
# Range for composer GKE Pods
pods = "172.21.0.0/17"
services = "172.21.128.0/20"
}
}
]
peering_config = {
peer_vpc_self_link = var.sample_vpc_self_link
import_routes = true
}
}
resource "google_composer_environment" "aef_composer_environment" {
count = var.create_composer_environment == true ? 1 : 0
provider = google-beta
project = var.project
name = "aef-${var.project}-${var.environment}"
region = var.region
config {
software_config {
airflow_config_overrides = var.composer_config.software_config.airflow_config_overrides
pypi_packages = var.composer_config.software_config.pypi_packages
env_variables = local.composer_env_variables
image_version = var.composer_config.software_config.image_version
cloud_data_lineage_integration {
enabled = var.composer_config.software_config.cloud_data_lineage_integration
}
}
workloads_config {
scheduler {
cpu = var.composer_config.workloads_config.scheduler.cpu
memory_gb = var.composer_config.workloads_config.scheduler.memory_gb
storage_gb = var.composer_config.workloads_config.scheduler.storage_gb
count = var.composer_config.workloads_config.scheduler.count
}
web_server {
cpu = var.composer_config.workloads_config.web_server.cpu
memory_gb = var.composer_config.workloads_config.web_server.memory_gb
storage_gb = var.composer_config.workloads_config.web_server.storage_gb
}
worker {
cpu = var.composer_config.workloads_config.worker.cpu
memory_gb = var.composer_config.workloads_config.worker.memory_gb
storage_gb = var.composer_config.workloads_config.worker.storage_gb
min_count = var.composer_config.workloads_config.worker.min_count
max_count = var.composer_config.workloads_config.worker.max_count
}
triggerer {
cpu = var.composer_config.workloads_config.worker.cpu
memory_gb = var.composer_config.workloads_config.worker.memory_gb
count = var.composer_config.workloads_config.scheduler.count
}
}
environment_size = var.composer_config.environment_size
node_config {
network = module.vpc.self_link
subnetwork = module.vpc.subnets["${var.region}/psoaef-composer-${var.region}"].id
service_account = module.composer-service-account[0].email
enable_ip_masq_agent = true
tags = ["composer-worker"]
ip_allocation_policy {
cluster_secondary_range_name = "pods"
services_secondary_range_name = "services"
}
}
private_environment_config {
enable_private_endpoint = "true"
cloud_sql_ipv4_cidr_block = "10.0.10.0/24"
master_ipv4_cidr_block = "10.0.11.0/28"
cloud_composer_connection_subnetwork = var.composer_config.connection_subnetwork
}
dynamic "encryption_config" {
for_each = var.composer_config.service_encryption_keys != null ? [""] : []
content {
kms_key_name = var.composer_config.service_encryption_keys
}
}
web_server_network_access_control {
dynamic "allowed_ip_range" {
for_each = var.composer_config.web_server_access_control
content {
value = allowed_ip_range.key
description = allowed_ip_range.value
}
}
}
}
depends_on = [
module.composer-service-account,
google_service_account_iam_member.custom_service_account,
module.vpc
]
}
module "composer-service-account" {
count = var.create_composer_environment == true ? 1 : 0
source = "github.com/GoogleCloudPlatform/cloud-foundation-fabric/modules/iam-service-account"
project_id = var.project
name = "aef-composer${var.environment}-sa"
iam_project_roles = {
"${var.project}" = [
"roles/bigquery.jobUser",
"roles/composer.worker",
"roles/dataform.admin",
"roles/dataflow.admin",
"roles/iam.serviceAccountUser",
"roles/composer.ServiceAgentV2Ext",
"roles/iam.serviceAccountTokenCreator",
"roles/dataproc.admin"
]
}
}
module "dataproc-service-account" {
count = var.create_composer_environment == true ? 1 : 0
source = "github.com/GoogleCloudPlatform/cloud-foundation-fabric/modules/iam-service-account"
project_id = var.project
name = "aef-dataproc${var.environment}-sa"
iam_project_roles = {
"${var.project}" = [
"roles/bigquery.jobUser",
"roles/dataproc.worker",
"roles/dataproc.admin",
"roles/compute.osLogin"
]
}
}
resource "google_service_account_iam_member" "custom_service_account" {
count = var.create_composer_environment == true ? 1 : 0
provider = google-beta
service_account_id = module.composer-service-account[0].name
role = "roles/composer.ServiceAgentV2Ext"
member = "serviceAccount:service-${data.google_project.project.number}@cloudcomposer-accounts.iam.gserviceaccount.com"
depends_on = [module.composer-service-account]
}