belt 0.3.29 → 0.3.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/belt/cli/destroy_command.rb +73 -0
- data/lib/belt/cli/environment_command.rb +87 -12
- data/lib/belt/cli/environment_config.rb +2 -0
- data/lib/belt/version.rb +1 -1
- data/lib/templates/environment/main.tf.erb +7 -6
- data/lib/templates/environment/terraform.tfvars.erb +3 -0
- data/lib/templates/environment/variables.tf.erb +6 -0
- data/lib/templates/module/dns.tf.erb +75 -17
- data/lib/templates/module/frontend.tf.erb +4 -4
- data/lib/templates/module/outputs.tf.erb +2 -2
- data/lib/templates/module/variables.tf.erb +6 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 79ffa332a2eb6408d42c4652d1643009edac470adb6eabbcb9e6818286276977
|
|
4
|
+
data.tar.gz: 145edae1f0f725c16232a648d64ad3224ac93baca18273648b901e7d4ef6ad66
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '09e7a47ed0e3d34e699cbd2e1c14785399015ea66cde8b21dfd194e86d09456df246da0363e0bc0e543ad06129938376c148e87edc6885b0b6c9d94645ffdcd3'
|
|
7
|
+
data.tar.gz: 16ec119be87259081c896497229cf3202901c3657ac4aea2f3e47e05ff3976d7130e23a99dcdf1c85b80f8d386243878e0acccd4bc9cb721b0ca8d8e189e4122
|
|
@@ -194,6 +194,26 @@ module Belt
|
|
|
194
194
|
exit 1
|
|
195
195
|
end
|
|
196
196
|
|
|
197
|
+
# Check if any environments are nested under this one
|
|
198
|
+
nested_children = find_nested_children(@name)
|
|
199
|
+
if nested_children.any?
|
|
200
|
+
puts "⚠ The following environments are nested under '#{@name}':"
|
|
201
|
+
nested_children.each { |child| puts " - #{child}" }
|
|
202
|
+
puts ''
|
|
203
|
+
puts ' Destroying the parent will leave them with broken DNS references.'
|
|
204
|
+
puts ' Consider destroying nested environments first:'
|
|
205
|
+
nested_children.each { |child| puts " belt destroy environment #{child}" }
|
|
206
|
+
puts ''
|
|
207
|
+
unless @force
|
|
208
|
+
print ' Continue anyway? [y/N] '
|
|
209
|
+
response = $stdin.gets&.strip&.downcase
|
|
210
|
+
unless response&.start_with?('y')
|
|
211
|
+
puts 'Cancelled.'
|
|
212
|
+
exit 0
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
197
217
|
# Check if terraform state exists (infra may still be live)
|
|
198
218
|
if !@skip_terraform && terraform_state_exists?(dir)
|
|
199
219
|
puts "⚠ Environment '#{@name}' appears to have active infrastructure."
|
|
@@ -241,6 +261,21 @@ module Belt
|
|
|
241
261
|
warn_about_dns_delegation(@name)
|
|
242
262
|
end
|
|
243
263
|
|
|
264
|
+
def find_nested_children(env_name)
|
|
265
|
+
nested_children = []
|
|
266
|
+
Dir.glob('infrastructure/*/terraform.tfvars').each do |tfvars_file|
|
|
267
|
+
next if tfvars_file.include?('/dns/')
|
|
268
|
+
next if tfvars_file.include?('/modules/')
|
|
269
|
+
|
|
270
|
+
content = File.read(tfvars_file)
|
|
271
|
+
next unless content =~ /^\s*parent_environment\s*=\s*"#{Regexp.escape(env_name)}"/
|
|
272
|
+
|
|
273
|
+
child_env = File.basename(File.dirname(tfvars_file))
|
|
274
|
+
nested_children << child_env
|
|
275
|
+
end
|
|
276
|
+
nested_children
|
|
277
|
+
end
|
|
278
|
+
|
|
244
279
|
def terraform_state_exists?(dir)
|
|
245
280
|
# Check for local .terraform directory (initialized state)
|
|
246
281
|
return true if Dir.exist?(File.join(dir, '.terraform'))
|
|
@@ -262,6 +297,23 @@ module Belt
|
|
|
262
297
|
end
|
|
263
298
|
|
|
264
299
|
def warn_about_dns_delegation(env_name)
|
|
300
|
+
# Check if this is a nested environment (DNS records are in parent's zone, handled by terraform)
|
|
301
|
+
tfvars_file = "infrastructure/#{env_name}/terraform.tfvars"
|
|
302
|
+
if File.exist?(tfvars_file)
|
|
303
|
+
tfvars_content = File.read(tfvars_file)
|
|
304
|
+
if tfvars_content =~ /^\s*parent_environment\s*=\s*"([^"]+)"/
|
|
305
|
+
parent_env = ::Regexp.last_match(1)
|
|
306
|
+
puts ''
|
|
307
|
+
puts "ℹ This was a nested environment under '#{parent_env}'."
|
|
308
|
+
puts ' The DNS A record in the parent zone was deleted by terraform destroy.'
|
|
309
|
+
return
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# Check if any other environments are nested under this one
|
|
314
|
+
warn_about_nested_children(env_name)
|
|
315
|
+
|
|
316
|
+
# Check for standalone environment DNS delegation
|
|
265
317
|
dns_tfvars = 'infrastructure/dns/terraform.tfvars'
|
|
266
318
|
return unless File.exist?(dns_tfvars)
|
|
267
319
|
|
|
@@ -279,6 +331,27 @@ module Belt
|
|
|
279
331
|
puts ' belt dns deploy'
|
|
280
332
|
end
|
|
281
333
|
|
|
334
|
+
def warn_about_nested_children(env_name)
|
|
335
|
+
nested_children = []
|
|
336
|
+
Dir.glob('infrastructure/*/terraform.tfvars').each do |tfvars_file|
|
|
337
|
+
next if tfvars_file.include?('/dns/')
|
|
338
|
+
|
|
339
|
+
content = File.read(tfvars_file)
|
|
340
|
+
next unless content =~ /^\s*parent_environment\s*=\s*"#{Regexp.escape(env_name)}"/
|
|
341
|
+
|
|
342
|
+
child_env = File.basename(File.dirname(tfvars_file))
|
|
343
|
+
nested_children << child_env
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
return if nested_children.empty?
|
|
347
|
+
|
|
348
|
+
puts ''
|
|
349
|
+
puts '⚠ The following environments were nested under this one:'
|
|
350
|
+
nested_children.each { |child| puts " - #{child}" }
|
|
351
|
+
puts ' They may have dangling DNS references. Consider destroying them first,'
|
|
352
|
+
puts ' or manually cleaning up their Route53 records.'
|
|
353
|
+
end
|
|
354
|
+
|
|
282
355
|
def run_terraform_destroy(dir)
|
|
283
356
|
puts "\n━━━ terraform destroy (#{@name}) ━━━"
|
|
284
357
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require 'fileutils'
|
|
4
4
|
require 'erb'
|
|
5
5
|
require_relative 'app_detection'
|
|
6
|
+
require_relative 'environment_config'
|
|
6
7
|
require_relative 'frontend_registry'
|
|
7
8
|
require_relative 'frontend_setup_command'
|
|
8
9
|
|
|
@@ -15,26 +16,49 @@ module Belt
|
|
|
15
16
|
|
|
16
17
|
def self.run(args)
|
|
17
18
|
env_name = args.shift
|
|
19
|
+
parent_env = args.shift # Optional: parent environment for nested subdomains
|
|
18
20
|
|
|
19
21
|
if env_name.nil? || env_name.empty?
|
|
20
|
-
puts 'Usage: belt generate environment <name>'
|
|
22
|
+
puts 'Usage: belt generate environment <name> [parent]'
|
|
21
23
|
puts "\nExamples:"
|
|
22
24
|
puts ' belt generate environment dev01'
|
|
23
25
|
puts ' belt generate environment staging'
|
|
24
26
|
puts ' belt generate environment prod'
|
|
27
|
+
puts ''
|
|
28
|
+
puts 'Nested environments (subdomain of existing environment):'
|
|
29
|
+
puts ' belt generate environment fizzy123 dev'
|
|
30
|
+
puts ' → Creates fizzy123.dev.example.com using dev\'s wildcard cert'
|
|
25
31
|
exit 1
|
|
26
32
|
end
|
|
27
33
|
|
|
28
|
-
|
|
34
|
+
# Validate parent environment exists if provided
|
|
35
|
+
if parent_env && !parent_env.empty?
|
|
36
|
+
parent_dir = "infrastructure/#{parent_env}"
|
|
37
|
+
unless Dir.exist?(parent_dir)
|
|
38
|
+
puts "✗ Parent environment '#{parent_env}' not found at #{parent_dir}/"
|
|
39
|
+
puts ' The parent environment must exist before creating a nested environment.'
|
|
40
|
+
exit 1
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
new(env_name, parent_environment: parent_env).generate
|
|
29
45
|
end
|
|
30
46
|
|
|
31
|
-
def initialize(env_name, quiet: false, domain: nil, announce: true)
|
|
47
|
+
def initialize(env_name, quiet: false, domain: nil, announce: true, parent_environment: nil)
|
|
32
48
|
@env_name = env_name.downcase.gsub(/[^a-z0-9_-]/, '')
|
|
33
49
|
@app_name = detect_app_name
|
|
34
|
-
@domain = domain
|
|
35
50
|
@quiet = quiet
|
|
36
51
|
@announce = announce
|
|
52
|
+
@parent_environment = parent_environment&.downcase&.gsub(/[^a-z0-9_-]/, '')
|
|
37
53
|
@state_bucket = resolve_state_bucket
|
|
54
|
+
|
|
55
|
+
# For nested envs, inherit domain and aws_profile from parent
|
|
56
|
+
if @parent_environment
|
|
57
|
+
load_parent_config
|
|
58
|
+
@domain = domain || @parent_domain
|
|
59
|
+
else
|
|
60
|
+
@domain = domain
|
|
61
|
+
end
|
|
38
62
|
end
|
|
39
63
|
|
|
40
64
|
def generate
|
|
@@ -46,6 +70,7 @@ module Belt
|
|
|
46
70
|
end
|
|
47
71
|
|
|
48
72
|
puts "Creating environment: #{@env_name}" unless @quiet
|
|
73
|
+
puts " (nested under #{@parent_environment})" if @parent_environment && !@quiet
|
|
49
74
|
FileUtils.mkdir_p(dest_dir)
|
|
50
75
|
|
|
51
76
|
templates.each do |template_name, dest_file|
|
|
@@ -54,19 +79,40 @@ module Belt
|
|
|
54
79
|
puts " create #{dest_path}" unless @quiet
|
|
55
80
|
end
|
|
56
81
|
|
|
82
|
+
# Generate belt.rb if we have an inherited aws_profile
|
|
83
|
+
if @parent_aws_profile
|
|
84
|
+
belt_rb_path = File.join(dest_dir, 'belt.rb')
|
|
85
|
+
write_belt_rb(belt_rb_path)
|
|
86
|
+
puts " create #{belt_rb_path}" unless @quiet
|
|
87
|
+
end
|
|
88
|
+
|
|
57
89
|
append_extra_frontend_outputs(File.join(dest_dir, 'outputs.tf'))
|
|
58
90
|
|
|
59
91
|
return if @quiet || !@announce
|
|
60
92
|
|
|
61
93
|
puts "\n✓ Environment '#{@env_name}' created!"
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
94
|
+
|
|
95
|
+
if @parent_environment
|
|
96
|
+
puts "\nThis is a nested environment under '#{@parent_environment}'."
|
|
97
|
+
puts "It will use the parent's wildcard certificate and hosted zone."
|
|
98
|
+
if @domain
|
|
99
|
+
puts "Domain will be: api.#{@env_name}.#{@parent_environment}.#{@domain}"
|
|
100
|
+
else
|
|
101
|
+
puts "Domain will be: #{@env_name}.#{@parent_environment}.<your-domain>"
|
|
102
|
+
end
|
|
103
|
+
puts "\nDeploy with:"
|
|
104
|
+
puts " belt deploy #{@env_name}"
|
|
105
|
+
puts "\nNo DNS delegation needed — the parent environment handles DNS."
|
|
106
|
+
else
|
|
107
|
+
puts "\nReview your settings in #{dest_dir}/terraform.tfvars:"
|
|
108
|
+
puts ' environment = "..." # environment name (defaults to directory name)'
|
|
109
|
+
puts ' domain = "..." # your domain (e.g., "myapp.com")'
|
|
110
|
+
puts "\nThen deploy:"
|
|
111
|
+
puts " belt deploy #{@env_name}"
|
|
112
|
+
puts "\nIf using multiple environments with a custom domain, run:"
|
|
113
|
+
puts ' belt dns generate'
|
|
114
|
+
puts 'to manage root domain delegation to per-environment zones.'
|
|
115
|
+
end
|
|
70
116
|
end
|
|
71
117
|
|
|
72
118
|
private
|
|
@@ -101,6 +147,35 @@ module Belt
|
|
|
101
147
|
bucket_from_sibling || bucket_from_aws || 'belt-terraform-state'
|
|
102
148
|
end
|
|
103
149
|
|
|
150
|
+
# Load domain and aws_profile from parent environment for nested envs
|
|
151
|
+
def load_parent_config
|
|
152
|
+
parent_dir = "infrastructure/#{@parent_environment}"
|
|
153
|
+
|
|
154
|
+
# Load domain from parent's terraform.tfvars
|
|
155
|
+
tfvars_path = File.join(parent_dir, 'terraform.tfvars')
|
|
156
|
+
if File.exist?(tfvars_path)
|
|
157
|
+
content = File.read(tfvars_path)
|
|
158
|
+
domain_match = content.match(/^\s*domain\s*=\s*"([^"]+)"/)
|
|
159
|
+
@parent_domain = domain_match[1] if domain_match
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Load aws_profile from parent's belt.rb
|
|
163
|
+
parent_config = EnvironmentConfig.load(@parent_environment)
|
|
164
|
+
@parent_aws_profile = parent_config.aws_profile if parent_config.aws_profile?
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Generate belt.rb with inherited aws_profile from parent
|
|
168
|
+
def write_belt_rb(path)
|
|
169
|
+
content = <<~RUBY
|
|
170
|
+
# frozen_string_literal: true
|
|
171
|
+
|
|
172
|
+
Belt.configure do |config|
|
|
173
|
+
config.aws_profile = "#{@parent_aws_profile}"
|
|
174
|
+
end
|
|
175
|
+
RUBY
|
|
176
|
+
File.write(path, content)
|
|
177
|
+
end
|
|
178
|
+
|
|
104
179
|
def bucket_from_sibling
|
|
105
180
|
Dir.glob('infrastructure/*/backend.tf').each do |f|
|
|
106
181
|
match = File.read(f).match(/bucket\s*=\s*"([^"]+)"/)
|
data/lib/belt/version.rb
CHANGED
|
@@ -35,14 +35,15 @@ provider "conveyor-belt" {
|
|
|
35
35
|
module "app" {
|
|
36
36
|
source = "../modules/app"
|
|
37
37
|
|
|
38
|
-
app_name
|
|
39
|
-
environment
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
app_name = var.app_name
|
|
39
|
+
environment = var.environment
|
|
40
|
+
parent_environment = var.parent_environment
|
|
41
|
+
aws_region = var.aws_region
|
|
42
|
+
domain = var.domain
|
|
43
|
+
enable_pitr = var.enable_pitr
|
|
43
44
|
deletion_protection = var.deletion_protection
|
|
44
45
|
frontend_urls = concat(
|
|
45
|
-
var.domain != "" ? ["https://${var.environment == "prod" ? var.domain : "${var.environment}.${var.domain}"}"] : [],
|
|
46
|
+
var.domain != "" ? ["https://${var.parent_environment != "" ? "${var.environment}.${var.parent_environment}.${var.domain}" : (var.environment == "prod" ? var.domain : "${var.environment}.${var.domain}")}"] : [],
|
|
46
47
|
var.environment == "prod" ? [] : ["http://localhost:3000"]
|
|
47
48
|
)
|
|
48
49
|
|
|
@@ -9,6 +9,12 @@ variable "environment" {
|
|
|
9
9
|
type = string
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
variable "parent_environment" {
|
|
13
|
+
description = "Parent environment for nested subdomains (e.g., 'dev' creates fizzy123.dev.example.com). Leave empty for standalone environments."
|
|
14
|
+
type = string
|
|
15
|
+
default = ""
|
|
16
|
+
}
|
|
17
|
+
|
|
12
18
|
variable "aws_region" {
|
|
13
19
|
description = "AWS region"
|
|
14
20
|
type = string
|
|
@@ -1,30 +1,58 @@
|
|
|
1
1
|
# DNS configuration for Belt application
|
|
2
|
-
# Convention:
|
|
2
|
+
# Convention:
|
|
3
|
+
# prod → api.mydomain.com (standalone, creates own zone + cert)
|
|
4
|
+
# dev → api.dev.mydomain.com (standalone, creates own zone + cert)
|
|
5
|
+
# fizzy123 (dev) → api-fizzy123.dev.mydomain.com (nested, uses parent zone + cert)
|
|
6
|
+
#
|
|
7
|
+
# Note: Nested envs use api-<env> prefix (not api.<env>) because SSL wildcards are
|
|
8
|
+
# single-level. The parent's *.dev.mydomain.com covers api-fizzy123.dev.mydomain.com
|
|
9
|
+
# but NOT api.fizzy123.dev.mydomain.com.
|
|
3
10
|
|
|
4
11
|
locals {
|
|
12
|
+
# S3 bucket names follow DNS rules — underscores are not allowed.
|
|
13
|
+
s3_safe_name = replace(var.app_name, "_", "-")
|
|
14
|
+
|
|
15
|
+
# Is this a nested environment?
|
|
16
|
+
is_nested = var.parent_environment != ""
|
|
17
|
+
|
|
18
|
+
# Parent domain for nested envs (e.g., "dev.featureparity.dev")
|
|
19
|
+
parent_domain = local.is_nested ? "${var.parent_environment}.${var.domain}" : ""
|
|
20
|
+
|
|
5
21
|
# Determine the domain for this environment
|
|
22
|
+
# Nested: fizzy123.dev.mydomain.com
|
|
23
|
+
# Standalone: dev.mydomain.com (or mydomain.com for prod)
|
|
6
24
|
app_domain = var.domain != "" ? (
|
|
7
|
-
|
|
25
|
+
local.is_nested ? "${var.environment}.${local.parent_domain}" : (
|
|
26
|
+
var.environment == "prod" ? var.domain : "${var.environment}.${var.domain}"
|
|
27
|
+
)
|
|
8
28
|
) : ""
|
|
9
29
|
|
|
10
|
-
# API subdomain:
|
|
11
|
-
|
|
30
|
+
# API subdomain:
|
|
31
|
+
# Standalone: api.dev.mydomain.com (covered by *.dev.mydomain.com)
|
|
32
|
+
# Nested: api-fizzy123.dev.mydomain.com (api- prefix keeps it single-level under wildcard)
|
|
33
|
+
api_domain = var.domain != "" ? (
|
|
34
|
+
local.is_nested ? "api-${var.environment}.${local.parent_domain}" : "api.${local.app_domain}"
|
|
35
|
+
) : ""
|
|
12
36
|
|
|
13
37
|
# Whether DNS is enabled
|
|
14
38
|
dns_enabled = var.domain != ""
|
|
15
39
|
}
|
|
16
40
|
|
|
41
|
+
# =============================================================================
|
|
42
|
+
# STANDALONE ENVIRONMENTS (no parent)
|
|
43
|
+
# =============================================================================
|
|
44
|
+
|
|
17
45
|
# --- Route53 Hosted Zone ---
|
|
18
46
|
# One zone per environment subdomain (or root domain for prod)
|
|
19
47
|
resource "aws_route53_zone" "app" {
|
|
20
|
-
count = local.dns_enabled ? 1 : 0
|
|
48
|
+
count = local.dns_enabled && !local.is_nested ? 1 : 0
|
|
21
49
|
name = local.app_domain
|
|
22
50
|
}
|
|
23
51
|
|
|
24
52
|
# --- ACM Certificate ---
|
|
25
53
|
# Wildcard cert covers the app domain + all subdomains (api.*, www.*, etc.)
|
|
26
54
|
resource "aws_acm_certificate" "app" {
|
|
27
|
-
count = local.dns_enabled ? 1 : 0
|
|
55
|
+
count = local.dns_enabled && !local.is_nested ? 1 : 0
|
|
28
56
|
domain_name = local.app_domain
|
|
29
57
|
subject_alternative_names = ["*.${local.app_domain}"]
|
|
30
58
|
validation_method = "DNS"
|
|
@@ -41,7 +69,7 @@ resource "aws_acm_certificate" "app" {
|
|
|
41
69
|
# Use ellipsis (...) to group duplicates, then take the first element since
|
|
42
70
|
# they're identical anyway.
|
|
43
71
|
resource "aws_route53_record" "cert_validation" {
|
|
44
|
-
for_each = local.dns_enabled ? {
|
|
72
|
+
for_each = local.dns_enabled && !local.is_nested ? {
|
|
45
73
|
for dvo in aws_acm_certificate.app[0].domain_validation_options : dvo.resource_record_name => {
|
|
46
74
|
name = dvo.resource_record_name
|
|
47
75
|
type = dvo.resource_record_type
|
|
@@ -49,24 +77,53 @@ resource "aws_route53_record" "cert_validation" {
|
|
|
49
77
|
}...
|
|
50
78
|
} : {}
|
|
51
79
|
|
|
52
|
-
zone_id
|
|
53
|
-
name
|
|
54
|
-
type
|
|
55
|
-
ttl
|
|
56
|
-
records
|
|
80
|
+
zone_id = aws_route53_zone.app[0].zone_id
|
|
81
|
+
name = each.value[0].name
|
|
82
|
+
type = each.value[0].type
|
|
83
|
+
ttl = 300
|
|
84
|
+
records = [each.value[0].record]
|
|
85
|
+
allow_overwrite = true
|
|
57
86
|
}
|
|
58
87
|
|
|
59
88
|
resource "aws_acm_certificate_validation" "app" {
|
|
60
|
-
count = local.dns_enabled ? 1 : 0
|
|
89
|
+
count = local.dns_enabled && !local.is_nested ? 1 : 0
|
|
61
90
|
certificate_arn = aws_acm_certificate.app[0].arn
|
|
62
91
|
validation_record_fqdns = [for record in aws_route53_record.cert_validation : record.fqdn]
|
|
63
92
|
}
|
|
64
93
|
|
|
94
|
+
# =============================================================================
|
|
95
|
+
# NESTED ENVIRONMENTS (uses parent zone + wildcard cert)
|
|
96
|
+
# =============================================================================
|
|
97
|
+
|
|
98
|
+
# Look up parent's hosted zone
|
|
99
|
+
data "aws_route53_zone" "parent" {
|
|
100
|
+
count = local.dns_enabled && local.is_nested ? 1 : 0
|
|
101
|
+
name = local.parent_domain
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# Look up parent's wildcard certificate
|
|
105
|
+
data "aws_acm_certificate" "parent" {
|
|
106
|
+
count = local.dns_enabled && local.is_nested ? 1 : 0
|
|
107
|
+
domain = local.parent_domain
|
|
108
|
+
statuses = ["ISSUED"]
|
|
109
|
+
most_recent = true
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
# =============================================================================
|
|
113
|
+
# API GATEWAY (shared logic, uses appropriate cert/zone)
|
|
114
|
+
# =============================================================================
|
|
115
|
+
|
|
65
116
|
# --- API Gateway Custom Domain ---
|
|
66
117
|
resource "aws_api_gateway_domain_name" "api" {
|
|
67
|
-
count
|
|
68
|
-
domain_name
|
|
69
|
-
|
|
118
|
+
count = local.dns_enabled ? 1 : 0
|
|
119
|
+
domain_name = local.api_domain
|
|
120
|
+
|
|
121
|
+
# Use parent's cert for nested, own cert for standalone
|
|
122
|
+
regional_certificate_arn = local.is_nested ? (
|
|
123
|
+
data.aws_acm_certificate.parent[0].arn
|
|
124
|
+
) : (
|
|
125
|
+
aws_acm_certificate_validation.app[0].certificate_arn
|
|
126
|
+
)
|
|
70
127
|
|
|
71
128
|
endpoint_configuration {
|
|
72
129
|
types = ["REGIONAL"]
|
|
@@ -74,9 +131,10 @@ resource "aws_api_gateway_domain_name" "api" {
|
|
|
74
131
|
}
|
|
75
132
|
|
|
76
133
|
# Route53 A record for api.<domain>
|
|
134
|
+
# For nested envs, this goes in the parent zone
|
|
77
135
|
resource "aws_route53_record" "api" {
|
|
78
136
|
count = local.dns_enabled ? 1 : 0
|
|
79
|
-
zone_id = aws_route53_zone.app[0].zone_id
|
|
137
|
+
zone_id = local.is_nested ? data.aws_route53_zone.parent[0].zone_id : aws_route53_zone.app[0].zone_id
|
|
80
138
|
name = local.api_domain
|
|
81
139
|
type = "A"
|
|
82
140
|
|
|
@@ -102,7 +102,7 @@ resource "aws_cloudfront_distribution" "<%= @tf_name %>" {
|
|
|
102
102
|
|
|
103
103
|
viewer_certificate {
|
|
104
104
|
cloudfront_default_certificate = <%= @include_dns ? '!local.dns_enabled' : 'true' %>
|
|
105
|
-
acm_certificate_arn = <%= @include_dns ? 'local.dns_enabled ? aws_acm_certificate_validation.app[0].certificate_arn : null' : 'null' %>
|
|
105
|
+
acm_certificate_arn = <%= @include_dns ? 'local.dns_enabled ? (local.is_nested ? data.aws_acm_certificate.parent[0].arn : aws_acm_certificate_validation.app[0].certificate_arn) : null' : 'null' %>
|
|
106
106
|
ssl_support_method = <%= @include_dns ? 'local.dns_enabled ? "sni-only" : null' : 'null' %>
|
|
107
107
|
minimum_protocol_version = <%= @include_dns ? 'local.dns_enabled ? "TLSv1.2_2021" : null' : 'null' %>
|
|
108
108
|
}
|
|
@@ -144,7 +144,7 @@ resource "aws_s3_bucket_policy" "<%= @tf_name %>" {
|
|
|
144
144
|
# Route53 record for frontend (app domain → CloudFront)
|
|
145
145
|
resource "aws_route53_record" "<%= @tf_name %>" {
|
|
146
146
|
count = local.dns_enabled ? 1 : 0
|
|
147
|
-
zone_id = aws_route53_zone.app[0].zone_id
|
|
147
|
+
zone_id = local.is_nested ? data.aws_route53_zone.parent[0].zone_id : aws_route53_zone.app[0].zone_id
|
|
148
148
|
name = local.app_domain
|
|
149
149
|
type = "A"
|
|
150
150
|
|
|
@@ -155,9 +155,9 @@ resource "aws_route53_record" "<%= @tf_name %>" {
|
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
# www record for prod only
|
|
158
|
+
# www record for prod only (standalone envs only — nested envs don't need www)
|
|
159
159
|
resource "aws_route53_record" "<%= @tf_name %>_www" {
|
|
160
|
-
count = local.dns_enabled && var.environment == "prod" ? 1 : 0
|
|
160
|
+
count = local.dns_enabled && var.environment == "prod" && !local.is_nested ? 1 : 0
|
|
161
161
|
zone_id = aws_route53_zone.app[0].zone_id
|
|
162
162
|
name = "www.${var.domain}"
|
|
163
163
|
type = "A"
|
|
@@ -19,6 +19,6 @@ output "app_domain" {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
output "name_servers" {
|
|
22
|
-
description = "NS records to configure at your registrar"
|
|
23
|
-
value = local.dns_enabled ? aws_route53_zone.app[0].name_servers : []
|
|
22
|
+
description = "NS records to configure at your registrar (standalone envs only)"
|
|
23
|
+
value = local.dns_enabled && !local.is_nested ? aws_route53_zone.app[0].name_servers : []
|
|
24
24
|
}
|
|
@@ -8,6 +8,12 @@ variable "environment" {
|
|
|
8
8
|
type = string
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
variable "parent_environment" {
|
|
12
|
+
description = "Parent environment for nested subdomains (e.g., 'dev' creates fizzy123.dev.example.com). Leave empty for standalone environments."
|
|
13
|
+
type = string
|
|
14
|
+
default = ""
|
|
15
|
+
}
|
|
16
|
+
|
|
11
17
|
variable "aws_region" {
|
|
12
18
|
description = "AWS region"
|
|
13
19
|
type = string
|