belt 0.2.8 → 0.2.9
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/backup_runner.rb +37 -54
- data/lib/belt/version.rb +1 -1
- 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: 37cf283dc30b43f875f8600eb4b00e46b5782f374d3be306d0c5adaec7e9024a
|
|
4
|
+
data.tar.gz: 1153f552cef87426f91afcf28647065017b77a09bdd9347241366ed9cc455b76
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4cafc0f29e0148e74950e9fe0898e74632f2563980f60ffece7bfbb58ec7187ec30f928a0958dff9d9832fe79b21e8a87e512f26c4cddfa58c1f243776cea5eb
|
|
7
|
+
data.tar.gz: 7850fc8e4c5921646c25f77f67fdfcae663e148ec597b514b095b637733b0e55f34182e8c64ea52f2294ed687b3a24f7b6a56462b30df07811a01ffae23da952
|
|
@@ -14,7 +14,7 @@ module Belt
|
|
|
14
14
|
@infra_dir = infra_dir
|
|
15
15
|
@app_name = app_name
|
|
16
16
|
@timestamp = Time.now.strftime('%Y%m%d-%H%M%S')
|
|
17
|
-
@backup_bucket = "#{@app_name}-backups-#{@env}"
|
|
17
|
+
@backup_bucket = sanitize_bucket_name("#{@app_name}-backups-#{@env}")
|
|
18
18
|
@errors = []
|
|
19
19
|
@summary = []
|
|
20
20
|
end
|
|
@@ -41,6 +41,8 @@ module Belt
|
|
|
41
41
|
|
|
42
42
|
puts " 📦 Creating backup bucket: #{@backup_bucket}"
|
|
43
43
|
|
|
44
|
+
errors_before = @errors.size
|
|
45
|
+
|
|
44
46
|
run_aws('s3', 'mb', "s3://#{@backup_bucket}", '--region', detect_region)
|
|
45
47
|
|
|
46
48
|
# Enable versioning
|
|
@@ -54,7 +56,11 @@ module Belt
|
|
|
54
56
|
'--public-access-block-configuration',
|
|
55
57
|
'BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true')
|
|
56
58
|
|
|
57
|
-
|
|
59
|
+
if @errors.size > errors_before
|
|
60
|
+
puts ' ⚠️ Backup bucket creation failed (see warnings below)'
|
|
61
|
+
else
|
|
62
|
+
puts ' ✅ Backup bucket created and secured'
|
|
63
|
+
end
|
|
58
64
|
end
|
|
59
65
|
|
|
60
66
|
# ─── DynamoDB ────────────────────────────────────────────────────
|
|
@@ -153,11 +159,11 @@ module Belt
|
|
|
153
159
|
|
|
154
160
|
def export_cognito_users(pool_id)
|
|
155
161
|
all_users = []
|
|
156
|
-
|
|
162
|
+
next_token = nil
|
|
157
163
|
|
|
158
164
|
loop do
|
|
159
|
-
args = ['aws', 'cognito-idp', 'list-users', '--user-pool-id', pool_id, '--max-
|
|
160
|
-
args += ['--
|
|
165
|
+
args = ['aws', 'cognito-idp', 'list-users', '--user-pool-id', pool_id, '--max-items', '60']
|
|
166
|
+
args += ['--starting-token', next_token] if next_token
|
|
161
167
|
|
|
162
168
|
output, status = Open3.capture2(*args)
|
|
163
169
|
break unless status.success?
|
|
@@ -165,8 +171,8 @@ module Belt
|
|
|
165
171
|
data = JSON.parse(output)
|
|
166
172
|
all_users.concat(data['Users'] || [])
|
|
167
173
|
|
|
168
|
-
|
|
169
|
-
break if
|
|
174
|
+
next_token = data['NextToken']
|
|
175
|
+
break if next_token.nil? || next_token.empty?
|
|
170
176
|
|
|
171
177
|
puts ' Fetching next page of users...'
|
|
172
178
|
end
|
|
@@ -327,69 +333,41 @@ module Belt
|
|
|
327
333
|
end
|
|
328
334
|
end
|
|
329
335
|
|
|
330
|
-
# ───
|
|
336
|
+
# ─── Table Discovery ──────────────────────────────────────────────
|
|
331
337
|
|
|
332
338
|
def resolve_table_names
|
|
333
|
-
@resolve_table_names ||=
|
|
339
|
+
@resolve_table_names ||= discover_tables_from_aws
|
|
334
340
|
end
|
|
335
341
|
|
|
336
|
-
def
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
names = try_terraform_table_output
|
|
342
|
-
return names if names
|
|
342
|
+
def discover_tables_from_aws
|
|
343
|
+
# List all DynamoDB tables matching the app-env prefix directly from AWS.
|
|
344
|
+
# This is the source of truth — no Terraform output maintenance required.
|
|
345
|
+
prefix = "#{@app_name}-#{@env}-"
|
|
346
|
+
sanitized_prefix = sanitize_bucket_name(prefix)
|
|
343
347
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
def try_terraform_table_output
|
|
349
|
-
output, status = Open3.capture2('terraform', 'output', '-json', 'dynamodb_table_names')
|
|
350
|
-
return nil unless status.success?
|
|
351
|
-
|
|
352
|
-
names = begin
|
|
353
|
-
JSON.parse(output)
|
|
354
|
-
rescue StandardError
|
|
355
|
-
nil
|
|
348
|
+
output, status = Open3.capture2('aws', 'dynamodb', 'list-tables', '--output', 'json')
|
|
349
|
+
unless status.success?
|
|
350
|
+
@errors << 'Failed to list DynamoDB tables from AWS'
|
|
351
|
+
return []
|
|
356
352
|
end
|
|
357
|
-
names.is_a?(Array) && names.any? ? Array(names) : nil
|
|
358
|
-
end
|
|
359
|
-
|
|
360
|
-
def fetch_table_names_from_all_outputs
|
|
361
|
-
output, status = Open3.capture2('terraform', 'output', '-json')
|
|
362
|
-
return [] unless status.success?
|
|
363
353
|
|
|
364
|
-
|
|
365
|
-
JSON.parse(output)
|
|
354
|
+
all_tables = begin
|
|
355
|
+
JSON.parse(output)['TableNames'] || []
|
|
366
356
|
rescue StandardError
|
|
367
|
-
|
|
368
|
-
end
|
|
369
|
-
|
|
370
|
-
# Look for dynamodb_table_names in output
|
|
371
|
-
if all_outputs['dynamodb_table_names']
|
|
372
|
-
val = all_outputs['dynamodb_table_names']['value']
|
|
373
|
-
return Array(val) if val
|
|
357
|
+
[]
|
|
374
358
|
end
|
|
375
359
|
|
|
376
|
-
|
|
377
|
-
all_outputs.each do |key, data|
|
|
378
|
-
next unless key.include?('table')
|
|
379
|
-
|
|
380
|
-
val = data['value']
|
|
381
|
-
return Array(val) if val.is_a?(Array) && val.any?
|
|
382
|
-
end
|
|
383
|
-
|
|
384
|
-
[]
|
|
360
|
+
all_tables.select { |t| t.start_with?(prefix) || t.start_with?(sanitized_prefix) }
|
|
385
361
|
end
|
|
386
362
|
|
|
363
|
+
# ─── Terraform Output Resolution ─────────────────────────────────
|
|
364
|
+
|
|
387
365
|
def resolve_cognito_pool_id
|
|
388
366
|
env_dir = File.join(@infra_dir, @env)
|
|
389
367
|
return nil unless Dir.exist?(env_dir)
|
|
390
368
|
|
|
391
369
|
Dir.chdir(env_dir) do
|
|
392
|
-
output, status = Open3.capture2('terraform', 'output', '-json', 'user_pool_id')
|
|
370
|
+
output, status = Open3.capture2('terraform', 'output', '-json', 'user_pool_id', err: File::NULL)
|
|
393
371
|
if status.success?
|
|
394
372
|
val = begin
|
|
395
373
|
JSON.parse(output)
|
|
@@ -400,7 +378,7 @@ module Belt
|
|
|
400
378
|
end
|
|
401
379
|
|
|
402
380
|
# Fallback: search all outputs
|
|
403
|
-
output, status = Open3.capture2('terraform', 'output', '-json')
|
|
381
|
+
output, status = Open3.capture2('terraform', 'output', '-json', err: File::NULL)
|
|
404
382
|
if status.success?
|
|
405
383
|
all_outputs = begin
|
|
406
384
|
JSON.parse(output)
|
|
@@ -477,6 +455,11 @@ module Belt
|
|
|
477
455
|
|
|
478
456
|
# ─── Helpers ─────────────────────────────────────────────────────
|
|
479
457
|
|
|
458
|
+
def sanitize_bucket_name(name)
|
|
459
|
+
# S3 bucket names must be DNS-compliant: lowercase, hyphens, no underscores
|
|
460
|
+
name.tr('_', '-').downcase.gsub(/[^a-z0-9\-.]/, '-').gsub(/-{2,}/, '-')
|
|
461
|
+
end
|
|
462
|
+
|
|
480
463
|
def short_table_name(table_name)
|
|
481
464
|
table_name.sub(/\A#{Regexp.escape(@app_name)}-#{Regexp.escape(@env)}-/, '')
|
|
482
465
|
end
|
data/lib/belt/version.rb
CHANGED