Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 13 additions & 10 deletions terraform/modules/cloud-build-docker/README.md
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
71 changes: 48 additions & 23 deletions terraform/modules/cloud-build-docker/build_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
]
Expand All @@ -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,
]
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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() == "":
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that not something that terraform can manage for us?

```bash
gcloud artifacts repositories create docker-images \
--repository-format=docker \
--location=us-central1 \
--project=your-gcp-project
```

## Usage

1. Set your project variables:
Expand Down Expand Up @@ -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)
}
```

Expand All @@ -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"
}
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a really generic name. Can we call it something more specific? If this is only going to be used for deploys, we could call it deploy-images.

}

# Output the built image information
Expand Down
3 changes: 2 additions & 1 deletion terraform/modules/cloud-build-docker/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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}"
}
6 changes: 3 additions & 3 deletions terraform/modules/cloud-build-docker/outputs.tf
Original file line number Diff line number Diff line change
@@ -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
}

Expand Down
8 changes: 7 additions & 1 deletion terraform/modules/cloud-build-docker/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}