diff --git a/terraform/modules/cloud-build-docker/README.md b/terraform/modules/cloud-build-docker/README.md index 5f3a6bd..83ebdc1 100644 --- a/terraform/modules/cloud-build-docker/README.md +++ b/terraform/modules/cloud-build-docker/README.md @@ -1,6 +1,6 @@ # terraform-cloud-build-docker-module -A reusable Terraform module for building Docker images using Google Cloud Build with branch-based caching and digest tracking. +A reusable Terraform module for building Docker images using Google Cloud Build with Artifact Registry, branch-based caching, and digest tracking. ## Features @@ -23,6 +23,8 @@ module "my_app_image" { dockerfile_path = "Dockerfile" project_id = "my-gcp-project" image_tag_suffix = "latest" + region = "us-central1" + repository = "docker-images" # Artifact Registry repository (must exist) } # Use the built image digest @@ -122,15 +124,14 @@ module "secure_app" { - `dockerfile_path` - Path to the Dockerfile relative to context_path ("Dockerfile") - `base_digest` - Base image digest for build args ("latest") -- `cloud_build_config` - Custom Cloud Build config file (null) -- `build_args` - Additional build arguments ({}) -- `cache_enabled` - Enable branch-based caching (true) +- `region` - GCP region for Cloud Build and Artifact Registry ("us-central1") +- `repository` - Artifact Registry repository name ("docker-images") ## Outputs -- `image_digest` - Full image digest (e.g., gcr.io/project/image@sha256:abc123...) -- `image_uri` - Image URI without tag (e.g., gcr.io/project/image) -- `image_tag` - Image URI with tag (e.g., gcr.io/project/image:latest) +- `image_digest` - Full image digest (e.g., us-central1-docker.pkg.dev/project/repo/image@sha256:abc123...) +- `image_uri` - Image URI without tag (e.g., us-central1-docker.pkg.dev/project/repo/image) +- `image_tag` - Image URI with tag (e.g., us-central1-docker.pkg.dev/project/repo/image:latest) - `image_name` - Name of the built image - `image_tag_suffix` - Tag suffix used for the image - `project_id` - Project ID where the image was built @@ -196,8 +197,9 @@ your-app-repo/ ### GCP Setup - Google Cloud Build API enabled -- Container Registry or Artifact Registry access -- Appropriate IAM permissions for Cloud Build +- Artifact Registry API enabled +- An Artifact Registry Docker repository created in the target region (e.g., `gcloud artifacts repositories create docker-images --repository-format=docker --location=us-central1`) +- Appropriate IAM permissions for Cloud Build and Artifact Registry ### Local Setup @@ -282,8 +284,9 @@ resource "google_cloud_run_v2_service" "app" { ### Permission Issues - Verify Cloud Build service account has necessary permissions -- Check Container Registry/Artifact Registry access +- Check Artifact Registry access (Cloud Build service account needs `roles/artifactregistry.writer`) - Ensure GCS buckets exist and are accessible +- Ensure the Artifact Registry repository exists in the specified region ### Cache Issues diff --git a/terraform/modules/cloud-build-docker/build_image.py b/terraform/modules/cloud-build-docker/build_image.py index ba829a3..fdd2405 100755 --- a/terraform/modules/cloud-build-docker/build_image.py +++ b/terraform/modules/cloud-build-docker/build_image.py @@ -27,23 +27,44 @@ def run_command(cmd, **kwargs): raise +def verify_repository_exists(repository, region, project_id): + """Verify that the Artifact Registry repository exists.""" + try: + run_command( + [ + "gcloud", + "artifacts", + "repositories", + "describe", + repository, + f"--location={region}", + f"--project={project_id}", + ] + ) + except subprocess.CalledProcessError: + raise RuntimeError( + f"Artifact Registry repository '{repository}' not found in {region}.\n" + f"Create it with:\n" + f" gcloud artifacts repositories create {repository} \\\n" + f" --repository-format=docker \\\n" + f" --location={region} \\\n" + f" --project={project_id}" + ) + + def get_image_digest(image_uri, tag, project_id): - """Query the digest of an existing image.""" + """Query the digest of an existing image from Artifact Registry.""" try: - # Use tags={tag} for exact match filtering (not tags:{tag} which is substring match) - # See: gcloud topic filters + # Use gcloud artifacts docker images describe to get the digest result = run_command( [ "gcloud", - "container", + "artifacts", + "docker", "images", - "list-tags", - image_uri, - "--filter", - f"tags={tag}", - "--limit", - "1", - "--format=get(digest)", + "describe", + f"{image_uri}:{tag}", + "--format=get(image_summary.digest)", "--project", project_id, ] @@ -57,22 +78,20 @@ def get_image_digest(image_uri, tag, project_id): def check_cache_tag_exists(image_uri, cache_tag, project_id): - """Check if a cache tag exists for the given image.""" + """Check if a cache tag exists for the given image in Artifact Registry.""" try: - # Use tags={cache_tag} for exact match filtering (not tags:{tag} which is substring match) - # See: gcloud topic filters + # Use gcloud artifacts docker tags list to check if the tag exists result = run_command( [ "gcloud", - "container", - "images", - "list-tags", + "artifacts", + "docker", + "tags", + "list", image_uri, "--filter", - f"tags={cache_tag}", - "--limit", - "1", - "--format=get(digest)", + f"tag={cache_tag}", + "--format=get(tag)", "--project", project_id, ] @@ -108,11 +127,15 @@ def build_image( image_tag_suffix, base_digest="latest", region="us-central1", + repository="docker-images", ): """Build a Docker image via Cloud Build and return its digest.""" - # Construct image URIs - image_uri = f"gcr.io/{project_id}/{image_name}" + # Verify the Artifact Registry repository exists before proceeding + verify_repository_exists(repository, region, project_id) + + # Construct image URIs using Artifact Registry format + image_uri = f"{region}-docker.pkg.dev/{project_id}/{repository}/{image_name}" image_tag = f"{image_uri}:{image_tag_suffix}" print(f"Building image: {image_name} with tag: {image_tag_suffix}", file=sys.stderr) @@ -226,6 +249,7 @@ def main(): image_tag_suffix = input_data["image_tag_suffix"] base_digest = input_data.get("base_digest", "latest") region = input_data.get("region", "us-central1") + repository = input_data.get("repository", "docker-images") # Validate that image_tag_suffix is not empty if not image_tag_suffix or image_tag_suffix.strip() == "": @@ -240,6 +264,7 @@ def main(): image_tag_suffix=image_tag_suffix, base_digest=base_digest, region=region, + repository=repository, ) # Return JSON output for Terraform diff --git a/terraform/modules/cloud-build-docker/examples/simple-build/README.md b/terraform/modules/cloud-build-docker/examples/simple-build/README.md index c5ea209..cde32ff 100644 --- a/terraform/modules/cloud-build-docker/examples/simple-build/README.md +++ b/terraform/modules/cloud-build-docker/examples/simple-build/README.md @@ -1,13 +1,26 @@ # Simple Build Example -This example demonstrates the basic usage of the Cloud Build Docker module to build a simple web application. +This example demonstrates the basic usage of the Cloud Build Docker module to build a simple web application using Artifact Registry. ## What this example creates - A Docker image built using Google Cloud Build +- Image stored in Artifact Registry - A simple nginx-based web application - Image digest output for use in other Terraform resources +## Prerequisites + +Before running this example, ensure you have: + +1. An Artifact Registry Docker repository created: + ```bash + gcloud artifacts repositories create docker-images \ + --repository-format=docker \ + --location=us-central1 \ + --project=your-gcp-project + ``` + ## Usage 1. Set your project variables: @@ -38,6 +51,8 @@ module "web_app_image" { context_path = "./app" project_id = var.project_id image_tag_suffix = "latest" + region = var.region + repository = "docker-images" # Artifact Registry repository (must exist) } ``` @@ -56,9 +71,9 @@ After running `terraform apply`, you'll get: ```hcl image_info = { - image_digest = "gcr.io/your-project/simple-web-app@sha256:abc123..." - image_uri = "gcr.io/your-project/simple-web-app" - image_tag = "gcr.io/your-project/simple-web-app:latest" + image_digest = "us-central1-docker.pkg.dev/your-project/docker-images/simple-web-app@sha256:abc123..." + image_uri = "us-central1-docker.pkg.dev/your-project/docker-images/simple-web-app" + image_tag = "us-central1-docker.pkg.dev/your-project/docker-images/simple-web-app:latest" } ``` diff --git a/terraform/modules/cloud-build-docker/examples/simple-build/main.tf b/terraform/modules/cloud-build-docker/examples/simple-build/main.tf index beb7eb1..9177392 100644 --- a/terraform/modules/cloud-build-docker/examples/simple-build/main.tf +++ b/terraform/modules/cloud-build-docker/examples/simple-build/main.tf @@ -24,6 +24,8 @@ module "web_app_image" { context_path = "./app" project_id = var.project_id image_tag_suffix = "latest" + region = var.region + repository = "docker-images" # Artifact Registry repository (must exist) } # Output the built image information diff --git a/terraform/modules/cloud-build-docker/main.tf b/terraform/modules/cloud-build-docker/main.tf index 09f610c..22ac6ea 100644 --- a/terraform/modules/cloud-build-docker/main.tf +++ b/terraform/modules/cloud-build-docker/main.tf @@ -29,6 +29,7 @@ data "external" "image_build" { image_tag_suffix = var.image_tag_suffix base_digest = var.base_digest region = var.region + repository = var.repository } # Trigger rebuild when any of these change @@ -43,6 +44,6 @@ data "external" "image_build" { # Local values for easy access locals { image_digest = data.external.image_build.result.digest - image_uri = "gcr.io/${var.project_id}/${var.image_name}" + image_uri = "${var.region}-docker.pkg.dev/${var.project_id}/${var.repository}/${var.image_name}" image_tag = "${local.image_uri}:${var.image_tag_suffix}" } diff --git a/terraform/modules/cloud-build-docker/outputs.tf b/terraform/modules/cloud-build-docker/outputs.tf index 535c17c..94b4dfb 100644 --- a/terraform/modules/cloud-build-docker/outputs.tf +++ b/terraform/modules/cloud-build-docker/outputs.tf @@ -1,17 +1,17 @@ # Outputs for the Cloud Build Docker module output "image_digest" { - description = "Full image digest (e.g., gcr.io/project/image@sha256:abc123...)" + description = "Full image digest (e.g., us-central1-docker.pkg.dev/project/repo/image@sha256:abc123...)" value = local.image_digest } output "image_uri" { - description = "Image URI without tag (e.g., gcr.io/project/image)" + description = "Image URI without tag (e.g., us-central1-docker.pkg.dev/project/repo/image)" value = local.image_uri } output "image_tag" { - description = "Image URI with tag (e.g., gcr.io/project/image:latest)" + description = "Image URI with tag (e.g., us-central1-docker.pkg.dev/project/repo/image:latest)" value = local.image_tag } diff --git a/terraform/modules/cloud-build-docker/variables.tf b/terraform/modules/cloud-build-docker/variables.tf index 0cd2d1b..dad7c28 100644 --- a/terraform/modules/cloud-build-docker/variables.tf +++ b/terraform/modules/cloud-build-docker/variables.tf @@ -41,7 +41,13 @@ variable "base_digest" { } variable "region" { - description = "The GCP region where Cloud Build jobs will run" + description = "The GCP region where Cloud Build jobs will run and where Artifact Registry is located" type = string default = "us-central1" } + +variable "repository" { + description = "Artifact Registry repository name (must already exist)" + type = string + default = "docker-images" +}