@@ -41,69 +41,23 @@ resource "google_project_iam_member" "cloud_run_secret_access" {
4141locals {
4242 project_name = trimprefix (var. project_id , " ai2-skiff2-" )
4343
44- # Prod (main branch) services — keys where deployment_environment is "prod"
45- prod_services = {
46- for key , svc in var . services : key => svc if svc . deployment_environment == " prod"
44+ domain_families = {
45+ allenai = " allen.ai"
46+ apps = " apps.allenai.org"
47+ pandajungle = " pandajungle.org"
4748 }
4849
49- # Branch (non-prod) services
50- branch_services = {
51- for key , svc in var . services : key => svc if svc . deployment_environment != " prod"
52- }
53-
54- # Non-default prod services (for subdomain routing)
55- non_default_prod_services = {
56- for key , svc in local . prod_services : key => svc if key != var . default_service
57- }
58-
59- # Exclude allow_delete services from the URL map (prevents bug where deleting a service errors because the url map prevents the backend from being deleted)
60- url_map_non_default_prod_services = {
61- for key , svc in local . non_default_prod_services : key => svc if ! svc . allow_delete
62- }
63-
64- url_map_branch_services = {
65- for key , svc in local . branch_services : key => svc if ! svc . allow_delete
66- }
67-
68- # Distinct branch names (non-prod)
69- branch_names = distinct ([for _, svc in local . branch_services : svc . deployment_environment ])
70-
71- # Default service key per branch (first service found for each branch)
72- branch_default_keys = {
73- for branch in local . branch_names : branch => [
74- for key , svc in local . branch_services : key if svc . deployment_environment == branch
75- ][0 ]
76- }
50+ primary_domain_key = " pandajungle"
7751
78- # Non-default branch services (for branch subdomain routing)
79- non_default_branch_services = {
80- for key , svc in local . url_map_branch_services : key => svc
81- if key != lookup (local. branch_default_keys , svc. deployment_environment , " " )
82- }
52+ # Base domains for this project (e.g., "myapp.allen.ai")
53+ base_domains = { for key , domain in local . domain_families : key => " ${ local . project_name } .${ domain } " }
8354
84- # All domains for the SSL certificate
85- all_domains = concat (
86- # pandajungle.org — prod
87- [" ${ local . project_name } .pandajungle.org" ],
88- [for key , _ in local . non_default_prod_services : " ${ _. name } .${ local . project_name } .pandajungle.org" ],
89-
90- # apps.allenai.org and allen.ai — prod
91- [" ${ local . project_name } .allen.ai" , " ${ local . project_name } .apps.allenai.org" ],
92- [for key , _ in local . non_default_prod_services : " ${ _. name } .${ local . project_name } .allen.ai" ],
93- [for key , _ in local . non_default_prod_services : " ${ _. name } .${ local . project_name } .apps.allenai.org" ],
94-
95- # Branch domains — branch.project.pandajungle.org for default service per branch
96- [for branch in local . branch_names : " ${ branch } .${ local . project_name } .pandajungle.org" ],
97- [for branch in local . branch_names : " ${ branch } .${ local . project_name } .allen.ai" ],
98- [for branch in local . branch_names : " ${ branch } .${ local . project_name } .apps.allenai.org" ],
99-
100- # Branch domains — branch.service.project.pandajungle.org for non-default services
101- [for key , svc in local . non_default_branch_services : " ${ svc . deployment_environment } .${ svc . name } .${ local . project_name } .pandajungle.org" ],
102- [for key , svc in local . non_default_branch_services : " ${ svc . deployment_environment } .${ svc . name } .${ local . project_name } .allen.ai" ],
103- [for key , svc in local . non_default_branch_services : " ${ svc . deployment_environment } .${ svc . name } .${ local . project_name } .apps.allenai.org" ],
104-
105- # Per-service custom domains (prod only)
106- flatten ([for _, svc in local . prod_services : svc . custom_domains ]),
55+ # All backend NEG IDs, keyed by backend name
56+ all_backends = merge (
57+ { for key , neg in google_compute_region_network_endpoint_group . url_mask : " url-mask-${ key } " => neg . id },
58+ { " default" = google_compute_region_network_endpoint_group.default_service.id },
59+ { for key , neg in google_compute_region_network_endpoint_group . branch_default : " branch-${ key } " => neg . id },
60+ { for key , neg in google_compute_region_network_endpoint_group . custom_domain : " custom-${ replace (key, " ." , " -" )} " => neg . id },
10761 )
10862}
10963
@@ -117,118 +71,164 @@ data "google_compute_security_policy" "cloud_armor" {
11771 project = var. project_id
11872}
11973
120- resource "google_compute_region_network_endpoint_group" "default" {
121- for_each = var. services
122- name = " ${ each . value . deployment_environment } -${ each . value . name } -neg"
74+ # Create URL Mask NEGs for base service URL on each domain
75+ # <service>.project.domain to the matching Cloud Run service by name.
76+ resource "google_compute_region_network_endpoint_group" "url_mask" {
77+ for_each = local. domain_families
78+ name = " ${ local . project_name } -${ each . key } -neg"
79+ network_endpoint_type = " SERVERLESS"
80+ region = var. region
81+ project = var. project_id
82+ cloud_run {
83+ url_mask = " <service>.${ local . base_domains [each . key ]} "
84+ }
85+ }
86+
87+ # Explicit NEG for prod default service (bare domain routing)
88+ resource "google_compute_region_network_endpoint_group" "default_service" {
89+ name = " ${ local . project_name } -default-${ var . default_service } -neg"
90+ network_endpoint_type = " SERVERLESS"
91+ region = var. region
92+ project = var. project_id
93+ cloud_run {
94+ service = " prod-${ var . default_service } "
95+ }
96+ lifecycle {
97+ create_before_destroy = true
98+ }
99+ }
100+
101+ # Explicit NEGs for branch default services (branch bare domain routing)
102+ resource "google_compute_region_network_endpoint_group" "branch_default" {
103+ for_each = toset (var. branch_environments )
104+ name = " ${ local . project_name } -${ each . value } -default-${ var . default_service } -neg"
105+ network_endpoint_type = " SERVERLESS"
106+ region = var. region
107+ project = var. project_id
108+ cloud_run {
109+ service = " ${ each . value } -${ var . default_service } "
110+ }
111+ lifecycle {
112+ create_before_destroy = true
113+ }
114+ }
115+
116+ # Explicit NEGs for custom domain mappings
117+ resource "google_compute_region_network_endpoint_group" "custom_domain" {
118+ for_each = var. custom_domain_mappings
119+ name = " ${ local . project_name } -custom-${ replace (each. key , " ." , " -" )} -neg"
123120 network_endpoint_type = " SERVERLESS"
124121 region = var. region
125122 project = var. project_id
126123 cloud_run {
127- service = " ${ each . value . deployment_environment } - ${ each . value . name } "
124+ service = each. value
128125 }
129126}
130127
131- # Custom URL map with host-based routing for service subdomains
128+ # URL Map
129+ #
132130resource "google_compute_url_map" "default" {
133131 name = " ${ local . project_name } -url-map"
134132 project = var. project_id
135133
136- # Default service handles project_name.pandajungle.org and any unmatched hosts
137- # NOTE: We construct self_links manually instead of referencing module.lb-http.backend_services[...].self_link
138- # to avoid an implicit Terraform dependency that causes GCP to reject backend deletion before the URL map is updated.
139- default_service = " projects/${ var . project_id } /global/backendServices/default-lb-backend-default"
134+ # Unmatched hosts fall through to the `primary_domain_key` URL mask backend
135+ default_service = " projects/${ var . project_id } /global/backendServices/default-lb-backend-url-mask-${ local . primary_domain_key } "
140136
141- # Route each non-default prod service 's subdomain to its backend
137+ # Route each domain family 's wildcard to its URL mask backend
142138 dynamic "host_rule" {
143- for_each = local. url_map_non_default_prod_services
139+ for_each = local. domain_families
144140 content {
145- hosts = concat (
146- [
147- " ${ host_rule . value . name } .${ local . project_name } .pandajungle.org" ,
148- " ${ host_rule . value . name } .${ local . project_name } .allen.ai" ,
149- " ${ host_rule . value . name } .${ local . project_name } .apps.allenai.org" ,
150- ],
151- host_rule. value . custom_domains ,
152- )
153- path_matcher = host_rule. key
141+ hosts = [" *.${ local . base_domains [host_rule . key ]} " ]
142+ path_matcher = " url-mask-${ host_rule . key } "
154143 }
155144 }
156145
157146 dynamic "path_matcher" {
158- for_each = local. url_map_non_default_prod_services
147+ for_each = local. domain_families
159148 content {
160- name = path_matcher. key
161- default_service = module . lb-http . backend_services [ path_matcher . key ] . self_link
149+ name = " url-mask- ${ path_matcher . key } "
150+ default_service = " projects/ ${ var . project_id } /global/backendServices/default- lb-backend-url-mask- ${ path_matcher . key } "
162151 }
163152 }
164153
165- # Route branch default service: branch.project.pandajungle.org
154+ # Bare domains -> prod default service
155+ host_rule {
156+ hosts = values (local. base_domains )
157+ path_matcher = " bare-domain"
158+ }
159+
160+ path_matcher {
161+ name = " bare-domain"
162+ default_service = " projects/${ var . project_id } /global/backendServices/default-lb-backend-default"
163+ }
164+
165+ # Branch bare domains -> branch default service
166166 dynamic "host_rule" {
167- for_each = local . branch_default_keys
167+ for_each = toset (var . branch_environments )
168168 content {
169- hosts = [
170- " ${ host_rule . key } .${ local . project_name } .pandajungle.org" ,
171- " ${ host_rule . key } .${ local . project_name } .allen.ai" ,
172- " ${ host_rule . key } .${ local . project_name } .apps.allenai.org" ,
173- ]
174- path_matcher = " branch-${ host_rule . key } "
169+ hosts = [for _, d in local . base_domains : " ${ host_rule . value } .${ d } " ]
170+ path_matcher = " branch-${ host_rule . value } "
175171 }
176172 }
177173
178174 dynamic "path_matcher" {
179- for_each = local . branch_default_keys
175+ for_each = toset (var . branch_environments )
180176 content {
181- name = " branch-${ path_matcher . key } "
182- default_service = module . lb-http . backend_services [ path_matcher . value ] . self_link
177+ name = " branch-${ path_matcher . value } "
178+ default_service = " projects/ ${ var . project_id } /global/backendServices/default- lb-backend-branch- ${ path_matcher . value } "
183179 }
184180 }
185181
186- # Route branch non-default services: branch. service.project.pandajungle.org
182+ # Custom domains -> explicit service backends
187183 dynamic "host_rule" {
188- for_each = local . non_default_branch_services
184+ for_each = var . custom_domain_mappings
189185 content {
190- hosts = [
191- " ${ host_rule . value . deployment_environment } .${ host_rule . value . name } .${ local . project_name } .pandajungle.org" ,
192- " ${ host_rule . value . deployment_environment } .${ host_rule . value . name } .${ local . project_name } .allen.ai" ,
193- " ${ host_rule . value . deployment_environment } .${ host_rule . value . name } .${ local . project_name } .apps.allenai.org" ,
194- ]
195- path_matcher = host_rule. key
186+ hosts = [host_rule . key ]
187+ path_matcher = " custom-${ replace (host_rule. key , " ." , " -" )} "
196188 }
197189 }
198190
199191 dynamic "path_matcher" {
200- for_each = local . non_default_branch_services
192+ for_each = var . custom_domain_mappings
201193 content {
202- name = path_matcher. key
203- default_service = " projects/${ var . project_id } /global/backendServices/default-lb-backend-${ path_matcher . key } "
194+ name = " custom- ${ replace ( path_matcher. key , " . " , " - " ) } "
195+ default_service = " projects/${ var . project_id } /global/backendServices/default-lb-backend-custom- ${ replace ( path_matcher. key , " . " , " - " ) } "
204196 }
205197 }
206198}
207199
208- resource "google_certificate_manager_certificate_map" "default" {
209- name = " ${ local . project_name } -cert-map"
200+ # Wildcard certs and cert map are managed by skiff2.
201+ # Per-project infra only handles custom domain certs.
202+ #
203+ data "google_certificate_manager_certificate_map" "default" {
204+ name = " ${ local . project_name } -wildcard-cert-map"
210205 project = var. project_id
211206}
212207
213- resource "google_certificate_manager_certificate" "default" {
214- for_each = toset (local. all_domains )
215- name = " ${ local . project_name } -cert-${ replace (each. value , " ." , " -" )} "
208+ # Individual certs for custom domains (HTTP/LB authorization — no DNS needed)
209+ resource "google_certificate_manager_certificate" "custom" {
210+ for_each = var. custom_domain_mappings
211+ name = " ${ local . project_name } -cert-${ replace (each. key , " ." , " -" )} "
216212 project = var. project_id
217213
218214 managed {
219- domains = [each . value ]
215+ domains = [each . key ]
220216 }
221217}
222218
223- resource "google_certificate_manager_certificate_map_entry" "default" {
224- for_each = toset (local. all_domains )
225- name = " ${ local . project_name } -entry-${ replace (each. value , " ." , " -" )} "
219+ # Cert map entries — custom domains only (wildcard entries managed by skiff2)
220+ resource "google_certificate_manager_certificate_map_entry" "custom" {
221+ for_each = var. custom_domain_mappings
222+ name = " ${ local . project_name } -entry-${ replace (each. key , " ." , " -" )} "
226223 project = var. project_id
227- map = google_certificate_manager_certificate_map. default . name
228- certificates = [google_certificate_manager_certificate . default [each . value ]. id ]
229- hostname = each. value
224+ map = data . google_certificate_manager_certificate_map . default . name
225+ certificates = [google_certificate_manager_certificate . custom [each . key ]. id ]
226+ hostname = each. key
230227}
231228
229+
230+ # Load Balancer
231+ #
232232module "lb-http" {
233233 source = " GoogleCloudPlatform/lb-http/google//modules/serverless_negs"
234234 version = " ~> 12.0"
@@ -242,15 +242,14 @@ module "lb-http" {
242242 address = data. google_compute_global_address . lb_ip . address
243243
244244 ssl = true
245- certificate_map = google_certificate_manager_certificate_map. default . id
245+ certificate_map = data . google_certificate_manager_certificate_map . default . id
246246 https_redirect = true
247247
248-
249248 create_url_map = false
250249 url_map = google_compute_url_map. default . self_link
251250
252251 backends = {
253- for key , svc in var . services : ( key == var . default_service ? " default " : key) => {
252+ for key , neg_id in local . all_backends : key => {
254253 protocol = " HTTPS"
255254 enable_cdn = false
256255 security_policy = data.google_compute_security_policy.cloud_armor.self_link
@@ -260,11 +259,7 @@ module "lb-http" {
260259 sample_rate = 1.0
261260 }
262261
263- groups = [
264- {
265- group = google_compute_region_network_endpoint_group.default[key].id
266- }
267- ]
262+ groups = [{ group = neg_id }]
268263
269264 iap_config = {
270265 enable = false
0 commit comments