belt 0.4.1 → 0.4.2
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/CHANGELOG.md +12 -0
- data/lib/belt/cli/setup_command.rb +1 -1
- data/lib/belt/cli/tables_command.rb +114 -4
- 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: e17e0755e497e26d8bd10604e6f050b0c6e0b69e3627276f8d83be984862d5da
|
|
4
|
+
data.tar.gz: b4249c4ac11715ae45a091c3eced0d0bad8ecd5d89a750edfaa8586ab6449758
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: abe5b18f7eeeb92bba2863746e7cc71d854078462274993403c4d7b93af81ff2c82a3b5c1db2fbdf537f8a139ec15964d76aad042505ade12ad64e6069f40a5a
|
|
7
|
+
data.tar.gz: dba1d0cca64620fc48b495ee844600e7a8e38c577ad3f27ada191263038f38a081a3d640f9a66e00fd97c138e1686ae91654d6995225d357bec8a5016e903f5e
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,18 @@
|
|
|
4
4
|
|
|
5
5
|
### Bug Fix
|
|
6
6
|
|
|
7
|
+
- **`belt setup tables` no longer silently clobbers hand-added tables/GSIs.**
|
|
8
|
+
Regenerating `dynamodb.tf` is a full overwrite from `lambda/models/*.rb`, so a
|
|
9
|
+
table or `global_secondary_index` added straight into the `.tf` file — one the
|
|
10
|
+
models don't declare — used to vanish on the next run. Dropping a live GSI is
|
|
11
|
+
not harmless: queries against it start failing. The generator now diffs the
|
|
12
|
+
existing file and, when the regen would remove infrastructure it can't
|
|
13
|
+
re-derive, it warns and lists exactly what would disappear. An interactive run
|
|
14
|
+
prompts before overwriting; a generator auto-sync refuses and leaves the file
|
|
15
|
+
untouched. Pass `--force` (`-f`/`--yes`/`-y`) to overwrite anyway. The
|
|
16
|
+
`dynamodb.tf` header comment now spells out this behavior too. (Fixes the
|
|
17
|
+
misleading "harmless diff" framing in the 0.4.0 notes below.)
|
|
18
|
+
|
|
7
19
|
- **Apply environment AWS profile in `belt deploy frontend`, `belt frontend env`, `belt logs`, and `belt server`**:
|
|
8
20
|
Standalone frontend deployment (`belt deploy frontend <env>`), frontend env generation (`belt frontend env <env>`),
|
|
9
21
|
log viewing (`belt logs`), and the local dev server (`belt server`) now load `infrastructure/<env>/belt.rb`
|
|
@@ -40,7 +40,7 @@ module Belt
|
|
|
40
40
|
|
|
41
41
|
Subcommands:
|
|
42
42
|
state Set up S3 bucket for Terraform state
|
|
43
|
-
tables Generate DynamoDB table definitions from
|
|
43
|
+
tables Generate DynamoDB table definitions from lambda/models/*.rb
|
|
44
44
|
frontend Generate S3 + CloudFront infrastructure for frontend hosting
|
|
45
45
|
|
|
46
46
|
Options for state:
|
|
@@ -15,7 +15,12 @@ module Belt
|
|
|
15
15
|
# dynamodb.tf lives in infrastructure/modules/app and uses var.environment.
|
|
16
16
|
EnvResolver.resolve(args)
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
# `--force`/`-y` overwrites dynamodb.tf even when the regen would drop a
|
|
19
|
+
# hand-added table or GSI. Without it, an interactive run prompts before
|
|
20
|
+
# clobbering and a quiet (generator) run refuses.
|
|
21
|
+
force = args.intersect?(%w[--force -f --yes -y])
|
|
22
|
+
|
|
23
|
+
new(force: force).run
|
|
19
24
|
end
|
|
20
25
|
|
|
21
26
|
# Automatically sync dynamodb.tf in the app module.
|
|
@@ -26,8 +31,9 @@ module Belt
|
|
|
26
31
|
new(quiet: true).run
|
|
27
32
|
end
|
|
28
33
|
|
|
29
|
-
def initialize(quiet: false)
|
|
34
|
+
def initialize(quiet: false, force: false)
|
|
30
35
|
@quiet = quiet
|
|
36
|
+
@force = force
|
|
31
37
|
end
|
|
32
38
|
|
|
33
39
|
def run
|
|
@@ -166,6 +172,16 @@ module Belt
|
|
|
166
172
|
# Skip if content is unchanged
|
|
167
173
|
return if existing_content == new_content
|
|
168
174
|
|
|
175
|
+
# Regenerating dynamodb.tf is a full overwrite. Anything hand-added to the
|
|
176
|
+
# file that the generator can't re-derive from the models — a GSI added
|
|
177
|
+
# straight into the .tf, or a table for a model that no longer exists — would
|
|
178
|
+
# silently vanish. Detect that and refuse (quiet) or prompt (interactive)
|
|
179
|
+
# unless --force was passed.
|
|
180
|
+
if existing_content
|
|
181
|
+
dropped = detect_dropped_infrastructure(existing_content, new_content)
|
|
182
|
+
return if dropped.any? && !safe_to_overwrite?(dest, dropped)
|
|
183
|
+
end
|
|
184
|
+
|
|
169
185
|
File.write(dest, new_content)
|
|
170
186
|
|
|
171
187
|
if @quiet
|
|
@@ -179,10 +195,104 @@ module Belt
|
|
|
179
195
|
end
|
|
180
196
|
end
|
|
181
197
|
|
|
198
|
+
# Compare the existing dynamodb.tf against the freshly rendered content and
|
|
199
|
+
# return a list of human-readable descriptions of infrastructure that exists
|
|
200
|
+
# today but wouldn't be regenerated — i.e. would be dropped by the overwrite.
|
|
201
|
+
#
|
|
202
|
+
# We only surface *removals*, since additions and edits are the whole point of
|
|
203
|
+
# re-running the generator. A removal, on the other hand, is usually a mistake:
|
|
204
|
+
# a GSI someone added by hand that the model doesn't declare.
|
|
205
|
+
def detect_dropped_infrastructure(existing_content, new_content)
|
|
206
|
+
dropped = []
|
|
207
|
+
|
|
208
|
+
old_tables = table_labels(existing_content)
|
|
209
|
+
new_tables = table_labels(new_content)
|
|
210
|
+
dropped.concat((old_tables - new_tables).map { |label| "table \"#{label}\"" })
|
|
211
|
+
|
|
212
|
+
# Only compare GSIs on tables that survive — a dropped table already
|
|
213
|
+
# accounts for its indexes, no need to list them twice.
|
|
214
|
+
(old_tables & new_tables).each do |label|
|
|
215
|
+
old_gsis = gsi_names(existing_content, label)
|
|
216
|
+
new_gsis = gsi_names(new_content, label)
|
|
217
|
+
dropped.concat((old_gsis - new_gsis).map { |gsi| "GSI \"#{gsi}\" on table \"#{label}\"" })
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
dropped
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Resource labels for every aws_dynamodb_table block in the content.
|
|
224
|
+
def table_labels(content)
|
|
225
|
+
content.scan(/resource\s+"aws_dynamodb_table"\s+"([^"]+)"/).flatten
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# GSI names declared inside the given table's resource block.
|
|
229
|
+
def gsi_names(content, label)
|
|
230
|
+
block = table_block(content, label)
|
|
231
|
+
return [] unless block
|
|
232
|
+
|
|
233
|
+
block.scan(/global_secondary_index\s*\{[^}]*?name\s*=\s*"([^"]+)"/m).flatten
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Extract the body of a single aws_dynamodb_table resource block by brace
|
|
237
|
+
# matching, so we can scope GSI lookups to one table.
|
|
238
|
+
def table_block(content, label)
|
|
239
|
+
marker = /resource\s+"aws_dynamodb_table"\s+"#{Regexp.escape(label)}"\s*\{/
|
|
240
|
+
match = content.match(marker)
|
|
241
|
+
return nil unless match
|
|
242
|
+
|
|
243
|
+
start = match.end(0)
|
|
244
|
+
depth = 1
|
|
245
|
+
idx = start
|
|
246
|
+
while idx < content.length && depth.positive?
|
|
247
|
+
case content[idx]
|
|
248
|
+
when '{' then depth += 1
|
|
249
|
+
when '}' then depth -= 1
|
|
250
|
+
end
|
|
251
|
+
idx += 1
|
|
252
|
+
end
|
|
253
|
+
content[start...(idx - 1)]
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Decide whether it's safe to overwrite dynamodb.tf when the regen would drop
|
|
257
|
+
# hand-added infrastructure. Returns true to proceed, false to abort the write.
|
|
258
|
+
def safe_to_overwrite?(dest, dropped)
|
|
259
|
+
return true if @force
|
|
260
|
+
|
|
261
|
+
warn_dropped(dest, dropped)
|
|
262
|
+
|
|
263
|
+
# A generator auto-sync must never silently destroy custom infra. Refuse and
|
|
264
|
+
# tell the user to resolve it deliberately.
|
|
265
|
+
if @quiet
|
|
266
|
+
puts ' ⚠ skipped dynamodb.tf — would drop hand-added infrastructure ' \
|
|
267
|
+
'(run `belt setup tables` to review)'
|
|
268
|
+
return false
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
print "\nOverwrite anyway and drop the above? [y/N] "
|
|
272
|
+
response = $stdin.gets&.strip&.downcase
|
|
273
|
+
return true if %w[y yes].include?(response)
|
|
274
|
+
|
|
275
|
+
puts '✗ Aborted. dynamodb.tf left unchanged.'
|
|
276
|
+
puts ' Move the custom definition into a model, or re-run with --force to overwrite.'
|
|
277
|
+
false
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def warn_dropped(dest, dropped)
|
|
281
|
+
puts "\n⚠ Regenerating #{dest} would DROP infrastructure not derived from your models:"
|
|
282
|
+
dropped.each { |d| puts " • #{d}" }
|
|
283
|
+
puts "\n This usually means a table or GSI was added to dynamodb.tf by hand."
|
|
284
|
+
puts ' Belt only tracks tables and indexes it can read from lambda/models/*.rb,'
|
|
285
|
+
puts ' so a hand-added definition is invisible to the generator and gets overwritten.'
|
|
286
|
+
end
|
|
287
|
+
|
|
182
288
|
def render_dynamodb(models)
|
|
183
289
|
blocks = models.map { |m| render_table(m) }
|
|
184
|
-
"# Auto-generated by Belt from model definitions\n" \
|
|
185
|
-
"
|
|
290
|
+
"# Auto-generated by Belt from model definitions in lambda/models/*.rb\n" \
|
|
291
|
+
"#\n" \
|
|
292
|
+
"# Do NOT edit manually. `belt setup tables` overwrites this whole file from\n" \
|
|
293
|
+
"# your models. Any table or GSI added here by hand (that a model doesn't\n" \
|
|
294
|
+
"# declare) will be DROPPED on the next regeneration. Define indexes on the\n" \
|
|
295
|
+
"# model via indexes(), belongs_to, or cognito_authenticatable instead.\n\n#{blocks.join("\n\n")}\n"
|
|
186
296
|
end
|
|
187
297
|
|
|
188
298
|
def render_table(model)
|
data/lib/belt/version.rb
CHANGED