terraform/modules/builder/main.tf (100 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.
locals {
repository_prefix = "${var.repository_region}-docker.pkg.dev/${var.project_id}/${var.repository_id}"
container_hash = {
for container, config in var.containers :
container => sha512(join("", concat([
for f in fileset(config.source, "**/*") : fileexists("${config.source}/${f}") ? filesha512("${config.source}/${f}") : sha512("")
], [
config.config_yaml == "" ? "" : sha512(config.config_yaml)
])))
}
container_tag = {
for container, config in var.containers :
container => "ver-${substr(local.container_hash[container], 0, 10)}"
}
container_image = {
for container, config in var.containers :
container => "${local.repository_prefix}/${container}:${local.container_tag[container]}"
}
container_status = {
for container, config in var.containers :
container => merge(config, {
"tag" = "${local.container_tag[container]}",
"image" = "${local.container_image[container]}",
"hash" = "${local.container_hash[container]}",
"config_yaml_file" = config.config_yaml == "" ? "" : "config.yaml",
})
}
}
#
# Enable Cloud Build and fetch the Service Account
#
resource "google_project_service" "project" {
project = var.project_id
service = "cloudbuild.googleapis.com"
}
resource "google_service_account" "cloudbuild_actor" {
project = var.project_id
account_id = "cloudbuild-actor"
display_name = "Cloudbuild custom service account"
}
resource "google_project_iam_member" "logging_member" {
project = var.project_id
role = "roles/logging.logWriter"
member = "serviceAccount:${google_service_account.cloudbuild_actor.email}"
}
#
# Grant permissions to the Artifact Repository
#
resource "google_artifact_registry_repository_iam_member" "repository_member" {
project = var.project_id
location = var.repository_region
repository = var.repository_id
role = "roles/artifactregistry.admin"
member = "serviceAccount:${google_service_account.cloudbuild_actor.email}"
}
#
# Create Cloud Build staging bucket and grant permissions
#
resource "random_string" "suffix" {
length = 4
special = false
upper = false
}
resource "google_storage_bucket" "cloudbuild" {
project = var.project_id
location = var.region
name = "${var.project_id}-${var.region}-cloudbuild-${random_string.suffix.id}"
uniform_bucket_level_access = true
force_destroy = true
}
resource "google_storage_bucket_iam_member" "gcs_cloudbuild_member" {
bucket = google_storage_bucket.cloudbuild.id
role = "roles/storage.admin"
member = "serviceAccount:${google_service_account.cloudbuild_actor.email}"
}
#
# Launch CloudBuild
#
resource "null_resource" "run_cloud_build" {
for_each = var.containers
depends_on = [
google_storage_bucket_iam_member.gcs_cloudbuild_member,
google_artifact_registry_repository_iam_member.repository_member,
google_project_service.project,
google_storage_bucket_iam_member.gcs_cloudbuild_member,
]
triggers = {
source_contents_hash = local.container_hash[each.key]
}
provisioner "local-exec" {
when = create
command = <<-EOT
# Exit on any error
set -e
# Write to config.yaml
if [ "${local.container_status[each.key].config_yaml_file}" != "" ]; then
cat > "${each.value.source}/${local.container_status[each.key].config_yaml_file}" <<EOF
${each.value.config_yaml}
EOF
fi
# Stage and build
gcloud builds submit \
--project ${var.project_id} \
--region ${var.region} \
--gcs-source-staging-dir gs://${google_storage_bucket.cloudbuild.id}/source/${each.key} \
--gcs-log-dir gs://${google_storage_bucket.cloudbuild.id}/logs/${each.key} \
--service-account=${google_service_account.cloudbuild_actor.id} \
--tag "${local.container_image[each.key]}" \
"${each.value.source}"
# Remove config.yaml
if [ "${local.container_status[each.key].config_yaml_file}" != "" ]; then
rm "${each.value.source}/${local.container_status[each.key].config_yaml_file}"
fi
EOT
}
}