Skip to content

Commit 8160c25

Browse files
committed
feat: add Docker support and update Terraform configurations for Cloud Run deployment
1 parent 0b79c83 commit 8160c25

File tree

7 files changed

+182
-21
lines changed

7 files changed

+182
-21
lines changed

src/.dockerignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
node_modules/
2+
npm-debug.log
3+
.git
4+
.gitignore
5+
.env
6+
.nyc_output
7+
*.md
8+
.DS_Store
9+
cloudbuild.yaml
10+
__tests__/
11+
coverage/

src/Dockerfile

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
FROM node:22-slim
2+
3+
# Set the working directory
4+
WORKDIR /app
5+
6+
# Copy package files first for better layer caching
7+
COPY package*.json ./
8+
9+
# Install dependencies (this layer will be cached unless package files change)
10+
RUN npm ci --only=production --quiet --no-fund --no-audit && npm cache clean --force
11+
12+
# Copy source code
13+
COPY . .
14+
15+
# Cloud Run expects the container to listen on PORT (default 8080)
16+
ENV PORT=8080
17+
18+
# Use Functions Framework to serve the app
19+
CMD ["npx", "functions-framework", "--target=app"]

terraform/dev/main.tf

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@ terraform {
33
bucket = "tfstate-httparchive"
44
prefix = "tech-report-apis/dev"
55
}
6+
required_providers {
7+
docker = {
8+
source = "kreuzwerker/docker"
9+
version = "3.6.2"
10+
}
11+
google = {
12+
source = "hashicorp/google"
13+
}
14+
}
615
}
716

817
provider "google" {
@@ -12,14 +21,14 @@ provider "google" {
1221
}
1322

1423
module "endpoints" {
15-
source = "./../modules/run-service"
16-
entry_point = "app"
17-
project = var.project
18-
environment = var.environment
19-
source_directory = "../../src"
20-
function_name = "tech-report-api"
21-
region = var.region
22-
min_instances = var.min_instances
24+
source = "./../modules/run-service"
25+
entry_point = "app"
26+
project = var.project
27+
environment = var.environment
28+
source_directory = "../../src"
29+
function_name = "tech-report-api"
30+
region = var.region
31+
min_instances = var.min_instances
2332
environment_variables = {
2433
"PROJECT" = var.project
2534
"DATABASE" = var.project_database
Lines changed: 116 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,49 @@
1+
terraform {
2+
required_providers {
3+
docker = {
4+
source = "kreuzwerker/docker"
5+
version = "3.6.2"
6+
}
7+
google = {
8+
source = "hashicorp/google"
9+
}
10+
archive = {
11+
source = "hashicorp/archive"
12+
}
13+
}
14+
}
15+
16+
# Get access token for Artifact Registry authentication
17+
data "google_client_config" "default" {}
18+
19+
# Configure Docker provider with GCP Artifact Registry authentication
20+
provider "docker" {
21+
registry_auth {
22+
address = "${var.region}-docker.pkg.dev"
23+
username = "oauth2accesstoken"
24+
password = data.google_client_config.default.access_token
25+
}
26+
}
27+
128
locals {
229
bucketName = "gcf-v2-uploads-226352634162-us-central1"
330
}
31+
432
data "archive_file" "source" {
33+
count = var.environment != "dev" ? 1 : 0
534
type = "zip"
635
source_dir = var.source_directory
736
output_path = "/tmp/${var.function_name}.zip"
837
}
938
resource "google_storage_bucket_object" "zip" {
10-
name = "${var.environment}-${var.function_name}-${data.archive_file.source.output_sha}"
39+
count = var.environment != "dev" ? 1 : 0
40+
name = "${var.environment}-${var.function_name}-${data.archive_file.source[0].output_sha}"
1141
bucket = local.bucketName
12-
source = data.archive_file.source.output_path
42+
source = data.archive_file.source[0].output_path
1343
}
1444

1545
resource "google_cloudfunctions2_function" "function" {
46+
count = var.environment != "dev" ? 1 : 0
1647
name = "${var.function_name}-${var.environment}"
1748
location = var.region
1849

@@ -23,28 +54,28 @@ resource "google_cloudfunctions2_function" "function" {
2354
source {
2455
storage_source {
2556
bucket = local.bucketName
26-
object = google_storage_bucket_object.zip.name
57+
object = google_storage_bucket_object.zip[0].name
2758
}
2859
}
2960
}
3061

3162
service_config {
3263
all_traffic_on_latest_revision = true
33-
available_memory = var.available_memory_mb
64+
available_memory = var.available_memory
3465
available_cpu = var.available_cpu
3566
ingress_settings = var.ingress_settings
3667

3768
environment_variables = var.environment_variables
3869

3970
min_instance_count = var.min_instances
4071
max_instance_count = var.max_instances
41-
timeout_seconds = var.timeout
72+
timeout_seconds = 60
4273
max_instance_request_concurrency = var.max_instance_request_concurrency
4374
service_account_email = var.service_account_email
4475
}
4576

4677
labels = {
47-
owner = "tech_report_api"
78+
owner = var.service_name
4879
environment = var.environment
4980
}
5081

@@ -54,15 +85,92 @@ resource "google_cloudfunctions2_function" "function" {
5485
}
5586

5687
data "google_cloud_run_service" "run-service" {
57-
name = google_cloudfunctions2_function.function.name
88+
count = var.environment != "dev" ? 1 : 0
89+
name = google_cloudfunctions2_function.function[0].name
5890
location = var.region
5991
depends_on = [google_cloudfunctions2_function.function]
6092
}
6193

94+
6295
resource "google_cloud_run_v2_service_iam_member" "allow_unauthenticated" {
96+
count = var.environment != "dev" ? 1 : 0
97+
project = var.project
98+
location = var.region
99+
name = data.google_cloud_run_service.run-service[0].name
100+
role = "roles/run.invoker"
101+
member = "allUsers"
102+
}
103+
104+
# Native run service
105+
106+
107+
# Calculate hash of source files to determine if rebuild is needed
108+
locals {
109+
source_files = fileset(path.root, "${var.source_directory}/*")
110+
source_hash = substr(sha1(join("", [for f in local.source_files : filesha1(f)])), 0, 8)
111+
}
112+
113+
# Build Docker image
114+
resource "docker_image" "function_image" {
115+
name = "${var.region}-docker.pkg.dev/${var.project}/report-api/${var.service_name}:${local.source_hash}"
116+
117+
build {
118+
context = var.source_directory
119+
dockerfile = "Dockerfile"
120+
platform = "linux/amd64"
121+
}
122+
}
123+
124+
resource "docker_registry_image" "registry_image" {
125+
name = docker_image.function_image.name
126+
}
127+
128+
resource "google_cloud_run_v2_service" "service" {
129+
name = "${var.service_name}-${var.environment}"
130+
location = var.region
131+
132+
deletion_protection = false
133+
ingress = "INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER"
134+
135+
template {
136+
service_account = var.service_account_email
137+
138+
containers {
139+
image = docker_registry_image.registry_image.name
140+
resources {
141+
limits = {
142+
cpu = var.available_cpu
143+
memory = var.available_memory
144+
}
145+
}
146+
dynamic "env" {
147+
for_each = var.environment_variables
148+
content {
149+
name = env.key
150+
value = env.value
151+
}
152+
}
153+
}
154+
timeout = var.timeout
155+
max_instance_request_concurrency = var.max_instance_request_concurrency
156+
}
157+
scaling {
158+
min_instance_count = var.min_instances
159+
}
160+
traffic {
161+
type = "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST"
162+
percent = 100
163+
}
164+
labels = {
165+
owner = var.service_name
166+
environment = var.environment
167+
}
168+
}
169+
170+
resource "google_cloud_run_v2_service_iam_member" "allow_unauthenticated_report_api" {
63171
project = var.project
64172
location = var.region
65-
name = data.google_cloud_run_service.run-service.name
173+
name = google_cloud_run_v2_service.service.name
66174
role = "roles/run.invoker"
67175
member = "allUsers"
68176
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

22
output "name" {
3-
description = "Name of the Cloud Function"
4-
value = google_cloudfunctions2_function.function.name
3+
description = "Name of the Cloud Run service"
4+
value = google_cloud_run_v2_service.service.name
55
}

terraform/modules/run-service/variables.tf

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ variable "region" {
55
default = "us-central1"
66
type = string
77
}
8+
variable "service_name" {
9+
description = "Optional: Can be used to create more than function from the same package"
10+
type = string
11+
default = "report-api"
12+
}
813
variable "environment" {
914
description = "The 'Environment' that is being created/deployed. Applied as a suffix to many resources."
1015
type = string
@@ -21,7 +26,7 @@ variable "entry_point" {
2126
description = "The entry point; This is either what is registered with 'http' or exported from the code as a handler!"
2227
type = string
2328
}
24-
variable "available_memory_mb" {
29+
variable "available_memory" {
2530
default = "1Gi"
2631
type = string
2732
description = "The amount of memory for the Cloud Function"
@@ -46,8 +51,8 @@ variable "project" {
4651
type = string
4752
}
4853
variable "timeout" {
49-
default = 60
50-
type = number
54+
default = "60s"
55+
type = string
5156
description = "Timeout (in seconds) for the function. Default value is 60 seconds. Cannot be more than 540 seconds."
5257
}
5358
variable "service_account_email" {

terraform/prod/main.tf

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@ terraform {
33
bucket = "tfstate-httparchive"
44
prefix = "tech-report-apis/prod"
55
}
6+
required_providers {
7+
docker = {
8+
source = "kreuzwerker/docker"
9+
version = "3.6.2"
10+
}
11+
google = {
12+
source = "hashicorp/google"
13+
}
14+
}
615
}
716

817
provider "google" {

0 commit comments

Comments
 (0)