belt 0.3.18 → 0.3.20
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/dns_command.rb +306 -0
- data/lib/belt/cli/environment_command.rb +3 -0
- data/lib/belt/cli/generate_command.rb +31 -1
- data/lib/belt/cli.rb +5 -0
- data/lib/belt/version.rb +1 -1
- data/lib/templates/dns/backend.tf.erb +8 -0
- data/lib/templates/dns/belt.rb.erb +15 -0
- data/lib/templates/dns/main.tf.erb +45 -0
- data/lib/templates/dns/outputs.tf.erb +14 -0
- data/lib/templates/dns/terraform.tfvars.erb +22 -0
- data/lib/templates/dns/variables.tf.erb +27 -0
- metadata +8 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 82023cea9ac6649d621b0830d5eb6f0be9185b479f4887abec7146780155953a
|
|
4
|
+
data.tar.gz: 3fbbe4cc88b68874f751d89f51c7f1e381171fa486c3cbdfa4a18331911584f2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 23ffe36bb364bff9e846eafc096bd0fd41a581442cbceade34bae462851ea461a2ac39ba99e73a3e875eb3d9b900a76527deac0413a1907f57f2647e4e8e13ad
|
|
7
|
+
data.tar.gz: fbb2663798c280a8aa0d369a2d7f25fdbc849c17ea293f541fca69bcd90e8c7ca441c5dfd4b1c0636c046a1ebc95ea8207abacf801a3a7b014efc3c6b676917c
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'erb'
|
|
5
|
+
require 'json'
|
|
6
|
+
require_relative 'app_detection'
|
|
7
|
+
require_relative 'environment_config'
|
|
8
|
+
|
|
9
|
+
module Belt
|
|
10
|
+
module CLI
|
|
11
|
+
class DnsCommand
|
|
12
|
+
TEMPLATE_DIR = File.expand_path('../../templates/dns', __dir__)
|
|
13
|
+
DNS_DIR = 'infrastructure/dns'
|
|
14
|
+
|
|
15
|
+
include AppDetection
|
|
16
|
+
|
|
17
|
+
def self.run(args)
|
|
18
|
+
subcommand = args.shift
|
|
19
|
+
|
|
20
|
+
case subcommand
|
|
21
|
+
when 'deploy', nil
|
|
22
|
+
new.deploy(args)
|
|
23
|
+
when 'generate', 'init'
|
|
24
|
+
new.generate
|
|
25
|
+
when 'add'
|
|
26
|
+
new.add_environment(args)
|
|
27
|
+
when '--help', '-h', 'help'
|
|
28
|
+
puts help
|
|
29
|
+
else
|
|
30
|
+
puts "Unknown dns subcommand: #{subcommand}\n\n#{help}"
|
|
31
|
+
exit 1
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.help
|
|
36
|
+
<<~HELP
|
|
37
|
+
Usage: belt dns <subcommand>
|
|
38
|
+
|
|
39
|
+
Subcommands:
|
|
40
|
+
deploy Deploy the dns infrastructure (init → plan → apply)
|
|
41
|
+
generate Create the infrastructure/dns directory (same as belt generate dns)
|
|
42
|
+
add <env> Add an environment's NS records to dns/terraform.tfvars
|
|
43
|
+
help Show this help
|
|
44
|
+
|
|
45
|
+
Examples:
|
|
46
|
+
belt dns deploy # Deploy the root zone
|
|
47
|
+
belt dns generate # Scaffold infrastructure/dns
|
|
48
|
+
belt dns add staging # Add staging's NS records to tfvars
|
|
49
|
+
|
|
50
|
+
The dns directory manages your root domain and delegates subdomains to
|
|
51
|
+
per-environment hosted zones. Each environment (dev, staging, prod) gets
|
|
52
|
+
its own Route 53 zone, and this root zone delegates to each of them.
|
|
53
|
+
|
|
54
|
+
Workflow:
|
|
55
|
+
1. belt deploy dev # Deploy environments first
|
|
56
|
+
2. belt dns generate # Create infrastructure/dns
|
|
57
|
+
3. belt dns add dev # Add dev's NS records
|
|
58
|
+
4. belt dns deploy # Deploy root zone
|
|
59
|
+
5. Update registrar NS records to output values
|
|
60
|
+
HELP
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def initialize(quiet: false)
|
|
64
|
+
@app_name = detect_app_name
|
|
65
|
+
@quiet = quiet
|
|
66
|
+
@state_bucket = resolve_state_bucket
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# --- Generate ---
|
|
70
|
+
def generate
|
|
71
|
+
if Dir.exist?(DNS_DIR)
|
|
72
|
+
puts "dns infrastructure already exists at #{DNS_DIR}/"
|
|
73
|
+
puts "\nTo configure it:"
|
|
74
|
+
puts " 1. Edit #{DNS_DIR}/terraform.tfvars with your domain and environment NS records"
|
|
75
|
+
puts ' 2. Run: belt dns deploy'
|
|
76
|
+
exit 1
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
puts 'Creating dns infrastructure...' unless @quiet
|
|
80
|
+
FileUtils.mkdir_p(DNS_DIR)
|
|
81
|
+
|
|
82
|
+
templates.each do |template_name, dest_file|
|
|
83
|
+
dest_path = File.join(DNS_DIR, dest_file)
|
|
84
|
+
write_template(template_name, dest_path)
|
|
85
|
+
puts " create #{dest_path}" unless @quiet
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
return if @quiet
|
|
89
|
+
|
|
90
|
+
puts "\n✓ dns infrastructure created!"
|
|
91
|
+
puts "\nThis manages your root domain and delegates subdomains to per-environment zones."
|
|
92
|
+
puts "\nNext steps:"
|
|
93
|
+
puts ' 1. Deploy your environments first (if not already deployed):'
|
|
94
|
+
puts ' belt deploy dev'
|
|
95
|
+
puts ' belt deploy staging'
|
|
96
|
+
puts ''
|
|
97
|
+
puts ' 2. Add each environment\'s NS records:'
|
|
98
|
+
puts ' belt dns add dev'
|
|
99
|
+
puts ' belt dns add staging'
|
|
100
|
+
puts ''
|
|
101
|
+
puts ' 3. Deploy the dns infrastructure:'
|
|
102
|
+
puts ' belt dns deploy'
|
|
103
|
+
puts ''
|
|
104
|
+
puts ' 4. Update your registrar\'s NS records to the root_name_servers output'
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# --- Deploy ---
|
|
108
|
+
def deploy(args)
|
|
109
|
+
unless Dir.exist?(DNS_DIR)
|
|
110
|
+
puts 'No infrastructure/dns directory found.'
|
|
111
|
+
puts "\nCreate it first:"
|
|
112
|
+
puts ' belt dns generate'
|
|
113
|
+
exit 1
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
auto = args.include?('--auto') || args.include?('-y')
|
|
117
|
+
env_config = load_dns_config
|
|
118
|
+
|
|
119
|
+
Dir.chdir(DNS_DIR) do
|
|
120
|
+
run_terraform('init', env_config)
|
|
121
|
+
run_terraform('plan', env_config, '-out=tfplan')
|
|
122
|
+
|
|
123
|
+
if auto
|
|
124
|
+
run_terraform('apply', env_config, 'tfplan')
|
|
125
|
+
else
|
|
126
|
+
print "\nApply this plan? [y/N] "
|
|
127
|
+
answer = $stdin.gets&.strip&.downcase
|
|
128
|
+
if %w[y yes].include?(answer)
|
|
129
|
+
run_terraform('apply', env_config, 'tfplan')
|
|
130
|
+
else
|
|
131
|
+
puts 'Aborted.'
|
|
132
|
+
exit 1
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# --- Add Environment ---
|
|
139
|
+
def add_environment(args)
|
|
140
|
+
env_name = args.shift
|
|
141
|
+
|
|
142
|
+
if env_name.nil? || env_name.start_with?('-')
|
|
143
|
+
puts 'Usage: belt dns add <env>'
|
|
144
|
+
puts "\nExample: belt dns add staging"
|
|
145
|
+
exit 1
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
env_dir = "infrastructure/#{env_name}"
|
|
149
|
+
unless Dir.exist?(env_dir)
|
|
150
|
+
puts "Environment #{env_name} not found at #{env_dir}/"
|
|
151
|
+
exit 1
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Get NS records from environment
|
|
155
|
+
ns_records = fetch_ns_records(env_name)
|
|
156
|
+
if ns_records.nil? || ns_records.empty?
|
|
157
|
+
puts "Could not fetch NS records for #{env_name}."
|
|
158
|
+
puts "\nMake sure the environment is deployed:"
|
|
159
|
+
puts " belt deploy #{env_name}"
|
|
160
|
+
exit 1
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Update terraform.tfvars
|
|
164
|
+
tfvars_path = "#{DNS_DIR}/terraform.tfvars"
|
|
165
|
+
unless File.exist?(tfvars_path)
|
|
166
|
+
puts "#{tfvars_path} not found. Run 'belt dns generate' first."
|
|
167
|
+
exit 1
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
update_tfvars(tfvars_path, env_name, ns_records)
|
|
171
|
+
puts "✓ Added #{env_name} NS records to #{tfvars_path}"
|
|
172
|
+
puts "\nRun 'belt dns deploy' to apply the changes."
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
private
|
|
176
|
+
|
|
177
|
+
def templates
|
|
178
|
+
{
|
|
179
|
+
'main.tf.erb' => 'main.tf',
|
|
180
|
+
'backend.tf.erb' => 'backend.tf',
|
|
181
|
+
'variables.tf.erb' => 'variables.tf',
|
|
182
|
+
'terraform.tfvars.erb' => 'terraform.tfvars',
|
|
183
|
+
'outputs.tf.erb' => 'outputs.tf',
|
|
184
|
+
'belt.rb.erb' => 'belt.rb'
|
|
185
|
+
}
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def write_template(template_name, dest_path)
|
|
189
|
+
template_path = File.join(TEMPLATE_DIR, template_name)
|
|
190
|
+
content = ERB.new(File.read(template_path), trim_mode: '-').result(binding)
|
|
191
|
+
File.write(dest_path, content)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def load_dns_config
|
|
195
|
+
belt_rb = File.join(DNS_DIR, 'belt.rb')
|
|
196
|
+
return {} unless File.exist?(belt_rb)
|
|
197
|
+
|
|
198
|
+
EnvironmentConfig.load(belt_rb)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def run_terraform(action, env_config, *extra_args)
|
|
202
|
+
cmd = ['terraform', action] + extra_args
|
|
203
|
+
env = {}
|
|
204
|
+
|
|
205
|
+
if env_config[:aws_profile]
|
|
206
|
+
env['AWS_PROFILE'] = env_config[:aws_profile]
|
|
207
|
+
puts "Using AWS profile: #{env_config[:aws_profile]}" if action == 'init'
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
system(env, *cmd) || begin
|
|
211
|
+
puts "\n✗ terraform #{action} failed"
|
|
212
|
+
exit 1
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def fetch_ns_records(env_name)
|
|
217
|
+
env_dir = "infrastructure/#{env_name}"
|
|
218
|
+
env_config = EnvironmentConfig.load(env_name)
|
|
219
|
+
|
|
220
|
+
env = {}
|
|
221
|
+
env['AWS_PROFILE'] = env_config.aws_profile if env_config.aws_profile?
|
|
222
|
+
|
|
223
|
+
Dir.chdir(env_dir) do
|
|
224
|
+
# Try terraform output first
|
|
225
|
+
output, status = Open3.capture2e(env, 'terraform', 'output', '-json', 'name_servers')
|
|
226
|
+
if status.success?
|
|
227
|
+
begin
|
|
228
|
+
return JSON.parse(output)
|
|
229
|
+
rescue JSON::ParserError
|
|
230
|
+
# Fall through to state inspection
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Fallback: inspect state for the zone
|
|
235
|
+
output, status = Open3.capture2e(env, 'terraform', 'state', 'show', '-json',
|
|
236
|
+
'module.app.aws_route53_zone.app[0]')
|
|
237
|
+
return nil unless status.success?
|
|
238
|
+
|
|
239
|
+
begin
|
|
240
|
+
state = JSON.parse(output)
|
|
241
|
+
state.dig('values', 'name_servers')
|
|
242
|
+
rescue JSON::ParserError
|
|
243
|
+
nil
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def update_tfvars(path, env_name, ns_records)
|
|
249
|
+
content = File.read(path)
|
|
250
|
+
|
|
251
|
+
# Format the NS records for HCL
|
|
252
|
+
ns_list = ns_records.map { |ns| " \"#{ns}\"" }.join(",\n")
|
|
253
|
+
new_entry = " #{env_name} = [\n#{ns_list}\n ]"
|
|
254
|
+
|
|
255
|
+
# Check if environment_zones already has entries
|
|
256
|
+
if content =~ /environment_zones\s*=\s*\{([^}]*)\}/m
|
|
257
|
+
existing = ::Regexp.last_match(1).strip
|
|
258
|
+
|
|
259
|
+
if existing.empty?
|
|
260
|
+
# Empty block - replace with our entry
|
|
261
|
+
content.sub!(/environment_zones\s*=\s*\{\s*\}/, "environment_zones = {\n#{new_entry}\n}")
|
|
262
|
+
elsif existing.include?("#{env_name} =")
|
|
263
|
+
# Environment already exists - update it
|
|
264
|
+
content.sub!(/#{env_name}\s*=\s*\[[^\]]*\]/m, "#{env_name} = [\n#{ns_list}\n ]")
|
|
265
|
+
else
|
|
266
|
+
# Add to existing entries
|
|
267
|
+
content.sub!(/environment_zones\s*=\s*\{/m, "environment_zones = {\n#{new_entry}")
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
File.write(path, content)
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# Resolve the state bucket name to use in backend.tf.
|
|
275
|
+
# Priority: existing sibling backend.tf → AWS account ID → bare placeholder.
|
|
276
|
+
def resolve_state_bucket
|
|
277
|
+
bucket_from_sibling || bucket_from_aws || 'belt-terraform-state'
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def bucket_from_sibling
|
|
281
|
+
Dir.glob('infrastructure/*/backend.tf').each do |f|
|
|
282
|
+
match = File.read(f).match(/bucket\s*=\s*"([^"]+)"/)
|
|
283
|
+
next unless match
|
|
284
|
+
# Skip the bare placeholder — it means state wasn't set up yet
|
|
285
|
+
return match[1] unless match[1] == 'belt-terraform-state'
|
|
286
|
+
end
|
|
287
|
+
nil
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def bucket_from_aws
|
|
291
|
+
require 'open3'
|
|
292
|
+
output, status = Open3.capture2e('aws', 'sts', 'get-caller-identity')
|
|
293
|
+
return nil unless status.success?
|
|
294
|
+
|
|
295
|
+
data = begin
|
|
296
|
+
JSON.parse(output)
|
|
297
|
+
rescue StandardError
|
|
298
|
+
nil
|
|
299
|
+
end
|
|
300
|
+
return nil unless data&.dig('Account')
|
|
301
|
+
|
|
302
|
+
"belt-terraform-state-#{data['Account']}"
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
end
|
|
@@ -64,6 +64,9 @@ module Belt
|
|
|
64
64
|
puts ' domain = "..." # your domain (e.g., "myapp.com")'
|
|
65
65
|
puts "\nThen deploy:"
|
|
66
66
|
puts " belt deploy #{@env_name}"
|
|
67
|
+
puts "\nIf using multiple environments with a custom domain, run:"
|
|
68
|
+
puts ' belt dns generate'
|
|
69
|
+
puts 'to manage root domain delegation to per-environment zones.'
|
|
67
70
|
end
|
|
68
71
|
|
|
69
72
|
private
|
|
@@ -4,6 +4,7 @@ require 'fileutils'
|
|
|
4
4
|
require 'erb'
|
|
5
5
|
require_relative 'app_detection'
|
|
6
6
|
require_relative 'auth_command'
|
|
7
|
+
require_relative 'dns_command'
|
|
7
8
|
require_relative 'environment_command'
|
|
8
9
|
require_relative 'frontend_command'
|
|
9
10
|
require_relative 'frontend_registry'
|
|
@@ -17,7 +18,7 @@ module Belt
|
|
|
17
18
|
class GenerateCommand
|
|
18
19
|
TEMPLATE_DIR = File.expand_path('../../templates/generate', __dir__)
|
|
19
20
|
|
|
20
|
-
GENERATORS = %w[scaffold resource model controller environment frontend views index auth].freeze
|
|
21
|
+
GENERATORS = %w[scaffold resource model controller environment frontend views index auth dns].freeze
|
|
21
22
|
|
|
22
23
|
include AppDetection
|
|
23
24
|
|
|
@@ -91,6 +92,32 @@ module Belt
|
|
|
91
92
|
Creates:
|
|
92
93
|
lambda/controllers/<app>/<name>_controller.rb Controller class
|
|
93
94
|
NOTES
|
|
95
|
+
},
|
|
96
|
+
'dns' => {
|
|
97
|
+
description: 'Generate infrastructure for managing root domain DNS with multi-environment delegation.',
|
|
98
|
+
usage: 'belt generate dns',
|
|
99
|
+
options: [],
|
|
100
|
+
examples: [
|
|
101
|
+
['belt g dns']
|
|
102
|
+
],
|
|
103
|
+
notes: <<~NOTES
|
|
104
|
+
Creates:
|
|
105
|
+
infrastructure/dns/ Root DNS zone infrastructure
|
|
106
|
+
infrastructure/dns/belt.rb AWS profile configuration
|
|
107
|
+
|
|
108
|
+
This enables multiple environments (dev, staging, prod) to each have their own
|
|
109
|
+
hosted zone while sharing a single domain. The root zone delegates subdomains
|
|
110
|
+
to per-environment zones.
|
|
111
|
+
|
|
112
|
+
Workflow:
|
|
113
|
+
1. Deploy environments first: belt deploy dev, belt deploy staging
|
|
114
|
+
2. Add NS records: belt dns add dev, belt dns add staging
|
|
115
|
+
3. Deploy: belt dns deploy
|
|
116
|
+
4. Update your registrar's NS records to the root_name_servers output
|
|
117
|
+
|
|
118
|
+
For multi-account setups where DNS lives in a shared account:
|
|
119
|
+
Edit infrastructure/dns/belt.rb to set the AWS profile.
|
|
120
|
+
NOTES
|
|
94
121
|
}
|
|
95
122
|
}.freeze
|
|
96
123
|
|
|
@@ -127,6 +154,8 @@ module Belt
|
|
|
127
154
|
|
|
128
155
|
return Belt::CLI::EnvironmentCommand.run(args) if generator == 'environment'
|
|
129
156
|
|
|
157
|
+
return Belt::CLI::DnsCommand.new.generate if generator == 'dns'
|
|
158
|
+
|
|
130
159
|
return Belt::CLI::FrontendCommand.run(args) if generator == 'frontend'
|
|
131
160
|
|
|
132
161
|
return Belt::CLI::ViewsCommand.run(args) if generator == 'views'
|
|
@@ -205,6 +234,7 @@ module Belt
|
|
|
205
234
|
controller Generate a controller
|
|
206
235
|
auth Generate Cognito user pool infrastructure
|
|
207
236
|
environment Create a new deployment environment
|
|
237
|
+
dns Generate root DNS zone with multi-environment delegation
|
|
208
238
|
frontend Scaffold a frontend app (react, vue, svelte; --name / --path for extras)
|
|
209
239
|
views Generate React pages for a resource
|
|
210
240
|
|
data/lib/belt/cli.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative 'version'
|
|
4
4
|
require_relative 'root'
|
|
5
|
+
require_relative 'cli/dns_command'
|
|
5
6
|
require_relative 'cli/env_resolver'
|
|
6
7
|
require_relative 'cli/new_command'
|
|
7
8
|
require_relative 'cli/generate_command'
|
|
@@ -45,6 +46,7 @@ module Belt
|
|
|
45
46
|
'plugin' => Belt::CLI::PluginCommand,
|
|
46
47
|
'explain' => Belt::CLI::ExplainCommand,
|
|
47
48
|
'deploy' => Belt::CLI::DeployCommand,
|
|
49
|
+
'dns' => Belt::CLI::DnsCommand,
|
|
48
50
|
'frontend' => Belt::CLI::FrontendEnvCommand,
|
|
49
51
|
%w[server s] => Belt::CLI::ServerCommand,
|
|
50
52
|
%w[version --version -v] => ->(_args) { puts "Belt #{Belt::VERSION}" }
|
|
@@ -103,6 +105,7 @@ module Belt
|
|
|
103
105
|
generate frontend <react|vue|svelte> Scaffold a frontend app [--name --path]
|
|
104
106
|
generate views <resource> [fields...] Generate React pages [--frontend NAME]
|
|
105
107
|
generate environment <name> Create a new environment
|
|
108
|
+
generate dns Generate root DNS zone infrastructure
|
|
106
109
|
destroy <scaffold|model|controller> <name> Remove generated components
|
|
107
110
|
destroy frontend [--frontend NAME] Remove a frontend directory
|
|
108
111
|
destroy views <resource> Remove React pages for a resource
|
|
@@ -111,6 +114,8 @@ module Belt
|
|
|
111
114
|
s Alias for server
|
|
112
115
|
deploy [environment] Deploy to AWS (init → plan → apply)
|
|
113
116
|
deploy frontend <env> [--frontend NAME] Build and deploy frontend(s) to AWS
|
|
117
|
+
dns deploy Deploy root DNS zone (init → plan → apply)
|
|
118
|
+
dns add <env> Add environment NS records to dns/terraform.tfvars
|
|
114
119
|
frontend env <env> [--frontend NAME] Write <frontend>/.env from terraform outputs
|
|
115
120
|
frontend list List configured frontends
|
|
116
121
|
routes [-g PATTERN] [-f json] Show route definitions
|
data/lib/belt/version.rb
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Belt.configure do |config|
|
|
4
|
+
# Set the AWS profile to use for DNS infrastructure.
|
|
5
|
+
# This can be a shared account for cross-environment DNS management,
|
|
6
|
+
# or the same profile as your environments for single-account setups.
|
|
7
|
+
#
|
|
8
|
+
# Example (shared DNS account):
|
|
9
|
+
# config.aws_profile = "sharedzilla"
|
|
10
|
+
#
|
|
11
|
+
# Example (same as environments):
|
|
12
|
+
# config.aws_profile = "myapp-dev"
|
|
13
|
+
#
|
|
14
|
+
# config.aws_profile = ""
|
|
15
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# DNS Root Zone Configuration
|
|
2
|
+
# This module manages the root domain and delegates subdomains to per-environment
|
|
3
|
+
# hosted zones. This enables multiple environments (dev, staging, prod, etc.) to
|
|
4
|
+
# each have their own DNS zone while sharing a single domain.
|
|
5
|
+
#
|
|
6
|
+
# SETUP:
|
|
7
|
+
# 1. Deploy each environment first: belt deploy dev, belt deploy staging, etc.
|
|
8
|
+
# 2. Get each environment's NS records: cd infrastructure/<env> && terraform output name_servers
|
|
9
|
+
# 3. Add them to terraform.tfvars below
|
|
10
|
+
# 4. Apply this module: cd infrastructure/dns-root && terraform apply
|
|
11
|
+
# 5. Update your registrar's NS records to the root_name_servers output
|
|
12
|
+
|
|
13
|
+
terraform {
|
|
14
|
+
required_version = ">= 1.0"
|
|
15
|
+
|
|
16
|
+
required_providers {
|
|
17
|
+
aws = {
|
|
18
|
+
source = "hashicorp/aws"
|
|
19
|
+
version = ">= 5.0"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
provider "aws" {
|
|
25
|
+
region = var.aws_region
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
# --- Root Hosted Zone ---
|
|
29
|
+
resource "aws_route53_zone" "root" {
|
|
30
|
+
name = var.domain
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# --- Subdomain NS Delegation ---
|
|
34
|
+
# For each environment, create NS records that delegate to that environment's
|
|
35
|
+
# hosted zone. This allows dev.domain.com, staging.domain.com, etc. to resolve
|
|
36
|
+
# independently.
|
|
37
|
+
resource "aws_route53_record" "env_delegation" {
|
|
38
|
+
for_each = var.environment_zones
|
|
39
|
+
|
|
40
|
+
zone_id = aws_route53_zone.root.zone_id
|
|
41
|
+
name = "${each.key}.${var.domain}"
|
|
42
|
+
type = "NS"
|
|
43
|
+
ttl = 300
|
|
44
|
+
records = each.value
|
|
45
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
output "root_zone_id" {
|
|
2
|
+
description = "Route53 Hosted Zone ID for the root domain"
|
|
3
|
+
value = aws_route53_zone.root.zone_id
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
output "root_name_servers" {
|
|
7
|
+
description = "NS records to configure at your registrar (name.com, GoDaddy, etc.)"
|
|
8
|
+
value = aws_route53_zone.root.name_servers
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
output "delegated_environments" {
|
|
12
|
+
description = "Environments with NS delegation configured"
|
|
13
|
+
value = keys(var.environment_zones)
|
|
14
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
domain = "" # Your root domain (e.g., "myapp.com")
|
|
2
|
+
|
|
3
|
+
# NS records from each environment's hosted zone.
|
|
4
|
+
# After deploying an environment, get its NS records:
|
|
5
|
+
# cd infrastructure/<env> && terraform output name_servers
|
|
6
|
+
#
|
|
7
|
+
# Then add them here:
|
|
8
|
+
# environment_zones = {
|
|
9
|
+
# dev = [
|
|
10
|
+
# "ns-xxx.awsdns-xx.org",
|
|
11
|
+
# "ns-xxx.awsdns-xx.co.uk",
|
|
12
|
+
# "ns-xxx.awsdns-xx.com",
|
|
13
|
+
# "ns-xxx.awsdns-xx.net"
|
|
14
|
+
# ]
|
|
15
|
+
# staging = [
|
|
16
|
+
# "ns-yyy.awsdns-yy.org",
|
|
17
|
+
# "ns-yyy.awsdns-yy.co.uk",
|
|
18
|
+
# "ns-yyy.awsdns-yy.com",
|
|
19
|
+
# "ns-yyy.awsdns-yy.net"
|
|
20
|
+
# ]
|
|
21
|
+
# }
|
|
22
|
+
environment_zones = {}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
variable "domain" {
|
|
2
|
+
description = "Root domain (e.g., myapp.com)"
|
|
3
|
+
type = string
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
variable "aws_region" {
|
|
7
|
+
description = "AWS region"
|
|
8
|
+
type = string
|
|
9
|
+
default = "us-east-1"
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
variable "environment_zones" {
|
|
13
|
+
description = <<-EOT
|
|
14
|
+
Map of environment names to their hosted zone NS records.
|
|
15
|
+
Get these values from each environment's Terraform output:
|
|
16
|
+
cd infrastructure/<env> && terraform output name_servers
|
|
17
|
+
|
|
18
|
+
Example:
|
|
19
|
+
environment_zones = {
|
|
20
|
+
dev = ["ns-1098.awsdns-09.org", "ns-2035.awsdns-62.co.uk", ...]
|
|
21
|
+
staging = ["ns-1318.awsdns-36.org", "ns-1762.awsdns-28.co.uk", ...]
|
|
22
|
+
prod = ["ns-111.awsdns-11.org", "ns-222.awsdns-22.co.uk", ...]
|
|
23
|
+
}
|
|
24
|
+
EOT
|
|
25
|
+
type = map(list(string))
|
|
26
|
+
default = {}
|
|
27
|
+
}
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: belt
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.20
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stowzilla
|
|
@@ -112,6 +112,7 @@ files:
|
|
|
112
112
|
- lib/belt/cli/contracts_command.rb
|
|
113
113
|
- lib/belt/cli/deploy_command.rb
|
|
114
114
|
- lib/belt/cli/destroy_command.rb
|
|
115
|
+
- lib/belt/cli/dns_command.rb
|
|
115
116
|
- lib/belt/cli/doctor_command.rb
|
|
116
117
|
- lib/belt/cli/env_resolver.rb
|
|
117
118
|
- lib/belt/cli/environment_command.rb
|
|
@@ -172,6 +173,12 @@ files:
|
|
|
172
173
|
- lib/belt/views/welcome/show.html.erb
|
|
173
174
|
- lib/belt_controller/base.rb
|
|
174
175
|
- lib/belt_controller/implicit_response.rb
|
|
176
|
+
- lib/templates/dns/backend.tf.erb
|
|
177
|
+
- lib/templates/dns/belt.rb.erb
|
|
178
|
+
- lib/templates/dns/main.tf.erb
|
|
179
|
+
- lib/templates/dns/outputs.tf.erb
|
|
180
|
+
- lib/templates/dns/terraform.tfvars.erb
|
|
181
|
+
- lib/templates/dns/variables.tf.erb
|
|
175
182
|
- lib/templates/environment/backend.tf.erb
|
|
176
183
|
- lib/templates/environment/main.tf.erb
|
|
177
184
|
- lib/templates/environment/outputs.tf.erb
|