belt 0.3.31 → 0.3.33

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b3545e62c7890adc16931218212e284d8e68994814be13ae0e231d5aee89b2ec
4
- data.tar.gz: 89b19c24b919d441bffbbb8cd426697e656b8726596c24c58cf5f03d79233c81
3
+ metadata.gz: efadc9dd5984c98b474ea0bda5f91617ccb9ad7077582086098d913d1190fb79
4
+ data.tar.gz: 8335192999b2f888a518e3bb542c04e0b23364523ecc788f8e6eda7b4281e136
5
5
  SHA512:
6
- metadata.gz: 7df1d7c18c7208d54cb0e37cbea0cf6c998f9b502950651c7e3e304f2786c3a750d664894d5f7f09cbc8848b550f365d7ebe415a5bcb701da45ef115fb7d28bb
7
- data.tar.gz: '05380ceae6d72a8874e66f52f616b527baa69f7101513842a6240765d2b1b6644bcd7b6d469acffeab246d18a152eabfb618a52c74753f5e61fd8cbbb663143a'
6
+ metadata.gz: f7ae2d5e4669eea772061e523bae6422388e356cbe1ecdf86f7d284b034549ac1127a239ac85cb8b3cf1babab27c0232ec61bcfc18dcac8ff54b2b03cb263435
7
+ data.tar.gz: 190079da2ca430955096baa50ea281da2feaee87e6ae24211aab5c26b559eea6f1d1f45d9c9dc627d7ebf5d28418681c13d0398ef744a970580cfe8441368f3b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.32
4
+
5
+ ### Enhancement
6
+
7
+ - **`belt destroy environment --full` for fully non-interactive teardown**: Added
8
+ a `--full` / `-y` flag that tears an environment down with zero prompts —
9
+ intended for CI pipelines and agents that spin up ephemeral per-PR
10
+ environments and destroy them on merge. Unlike `--force` (which skips
11
+ terraform and only removes local files), `--full` **always runs
12
+ `terraform destroy`** when terraform state is present, then deletes the local
13
+ `infrastructure/<env>/` files. It also auto-continues past the nested-children
14
+ warning. When no terraform state exists there is nothing to tear down, so the
15
+ destroy step is skipped and only the files are removed.
16
+
17
+ ```bash
18
+ belt destroy environment pr-1234 --full
19
+ ```
20
+
3
21
  ## 0.3.31
4
22
 
5
23
  ### Bug Fix
@@ -47,7 +47,7 @@ module Belt
47
47
  when 'environment'
48
48
  name = args.shift
49
49
  if name.nil? || name.empty?
50
- puts 'Usage: belt destroy environment <name> [--force] [--skip-terraform]'
50
+ puts 'Usage: belt destroy environment <name> [--full] [--force] [--skip-terraform]'
51
51
  exit 1
52
52
  end
53
53
  flags = parse_environment_flags(args)
@@ -79,13 +79,15 @@ module Belt
79
79
  end
80
80
 
81
81
  def self.parse_environment_flags(args)
82
- flags = { force: false, skip_terraform: false }
82
+ flags = { force: false, skip_terraform: false, full: false }
83
83
  args.each do |arg|
84
84
  case arg
85
85
  when '--force', '-f'
86
86
  flags[:force] = true
87
87
  when '--skip-terraform'
88
88
  flags[:skip_terraform] = true
89
+ when '--full', '-y'
90
+ flags[:full] = true
89
91
  end
90
92
  end
91
93
  flags
@@ -114,7 +116,12 @@ module Belt
114
116
  views Remove React pages for a resource
115
117
 
116
118
  Environment options:
117
- --force, -f Skip all prompts (CI mode)
119
+ --full, -y Non-interactive teardown: ALWAYS run terraform
120
+ destroy, then delete local files AND purge the
121
+ remote terraform state from S3, no prompts
122
+ (use this for CI / agents tearing down ephemeral envs)
123
+ --force, -f Skip all prompts but SKIP terraform destroy —
124
+ only deletes local files (leaves cloud resources)
118
125
  --skip-terraform Delete local files without running terraform destroy
119
126
 
120
127
  Examples:
@@ -124,6 +131,7 @@ module Belt
124
131
  belt d environment staging
125
132
  belt d environment dev --skip-terraform
126
133
  belt d environment dev --force
134
+ belt d environment pr-1234 --full # full non-interactive teardown
127
135
  belt d frontend
128
136
  belt d frontend --frontend ops
129
137
  belt d views post
@@ -133,16 +141,18 @@ module Belt
133
141
 
134
142
  For environments: if terraform state is detected, you'll be prompted to run
135
143
  `terraform destroy` first to tear down cloud resources before removing files.
144
+ Pass --full to run the whole teardown non-interactively (destroy + delete).
136
145
  HELP
137
146
  end
138
147
 
139
148
  # -- keyword options for destroy flags
140
- def initialize(generator, name, fields, force: false, skip_terraform: false, frontend_name: nil)
149
+ def initialize(generator, name, fields, force: false, skip_terraform: false, full: false, frontend_name: nil)
141
150
  @generator = generator
142
151
  @name = name&.downcase&.gsub(/[^a-z0-9_]/, '_')
143
152
  @fields = fields
144
153
  @force = force
145
154
  @skip_terraform = skip_terraform
155
+ @full = full
146
156
  @frontend_name = frontend_name
147
157
  @app_name = detect_namespace
148
158
  @singular_name = @name ? Belt::Inflector.singularize(@name) : nil
@@ -205,7 +215,7 @@ module Belt
205
215
  puts ' Consider destroying nested environments first:'
206
216
  nested_children.each { |child| puts " belt destroy environment #{child}" }
207
217
  puts ''
208
- unless @force
218
+ unless @force || @full
209
219
  print ' Continue anyway? [y/N] '
210
220
  response = $stdin.gets&.strip&.downcase
211
221
  unless response&.start_with?('y')
@@ -227,7 +237,12 @@ module Belt
227
237
  puts "⚠ Environment '#{@name}' appears to have active infrastructure."
228
238
  puts " Terraform state was found — resources may still be running.\n\n"
229
239
 
230
- if @force
240
+ if @full
241
+ # Fully non-interactive teardown (CI / agents): always run terraform
242
+ # destroy, then delete files. This is the flag to reach for when an
243
+ # automated workflow spins an environment up and tears it down.
244
+ run_terraform_destroy(dir)
245
+ elsif @force
231
246
  puts ' --force passed, skipping terraform destroy.'
232
247
  else
233
248
  puts ' Options:'
@@ -251,7 +266,7 @@ module Belt
251
266
  end
252
267
 
253
268
  # Final confirmation before deleting files
254
- unless @force
269
+ unless @force || @full
255
270
  print "\nPermanently delete #{dir}/? [y/N] "
256
271
  response = $stdin.gets&.strip&.downcase
257
272
  unless response&.start_with?('y')
@@ -260,6 +275,9 @@ module Belt
260
275
  end
261
276
  end
262
277
 
278
+ # Purge remote state from S3 when --full is used (complete ephemeral cleanup)
279
+ purge_terraform_state(dir) if @full
280
+
263
281
  FileUtils.rm_rf(dir)
264
282
  @removed << dir
265
283
  puts " remove #{dir}/"
@@ -388,6 +406,50 @@ module Belt
388
406
  puts ' ✓ Infrastructure destroyed.'
389
407
  end
390
408
 
409
+ # Purge the terraform state file from S3 after a full destroy.
410
+ # This leaves zero trace of ephemeral environments in the state bucket.
411
+ def purge_terraform_state(dir)
412
+ backend_file = File.join(dir, 'backend.tf')
413
+ return unless File.exist?(backend_file)
414
+
415
+ content = File.read(backend_file)
416
+
417
+ # Extract bucket and key from backend config
418
+ bucket_match = content.match(/bucket\s*=\s*"([^"]+)"/)
419
+ key_match = content.match(/key\s*=\s*"([^"]+)"/)
420
+
421
+ return unless bucket_match && key_match
422
+
423
+ bucket = bucket_match[1]
424
+ key = key_match[1]
425
+
426
+ puts ' Purging terraform state from S3...'
427
+
428
+ # Delete the state file
429
+ result = `aws s3 rm "s3://#{bucket}/#{key}" 2>&1`
430
+ if Process.last_status.success?
431
+ puts " ✓ Deleted s3://#{bucket}/#{key}"
432
+ else
433
+ puts " ⚠ Could not delete state file: #{result.strip}" unless result.include?('delete: s3://')
434
+ end
435
+
436
+ # Also try to delete the state lock file if it exists (DynamoDB-based locking
437
+ # uses a separate mechanism, but some setups have .tflock files)
438
+ lock_key = key.sub(/\.tfstate$/, '.tfstate.lock')
439
+ `aws s3 rm "s3://#{bucket}/#{lock_key}" 2>/dev/null`
440
+
441
+ # Try to clean up the environment's directory in the bucket if empty
442
+ # (e.g., if the key is "myapp/fizzy123/terraform.tfstate", try to remove the "fizzy123" prefix)
443
+ env_prefix = key.sub(%r{/[^/]+$}, '')
444
+ return unless env_prefix != key
445
+
446
+ # List remaining objects under this prefix
447
+ remaining = `aws s3 ls "s3://#{bucket}/#{env_prefix}/" 2>/dev/null`
448
+ return unless Process.last_status.success? && remaining.strip.empty?
449
+
450
+ puts " ✓ State directory s3://#{bucket}/#{env_prefix}/ is now empty"
451
+ end
452
+
391
453
  # Load infrastructure/<env>/belt.rb and apply its aws_profile + env vars to
392
454
  # the current process so terraform (and any aws CLI calls during destroy)
393
455
  # authenticate against the right account. Mirrors DeployCommand / TerraformCommand.
@@ -106,6 +106,34 @@ belt destroy frontend --frontend ops
106
106
  belt destroy views post
107
107
  ```
108
108
 
109
+ ### Non-interactive environment teardown
110
+
111
+ `belt destroy environment` is interactive by default: it prompts before running
112
+ `terraform destroy` and again before deleting local files. For CI pipelines and
113
+ agents that spin up ephemeral per-PR environments and tear them down on merge,
114
+ use `--full`:
115
+
116
+ ```bash
117
+ belt destroy environment pr-1234 --full
118
+ ```
119
+
120
+ `--full` (alias `-y`) runs the whole teardown with zero prompts: it **always runs
121
+ `terraform destroy`** (when terraform state is present), then deletes the local
122
+ `infrastructure/<env>/` files.
123
+
124
+ Flag comparison:
125
+
126
+ | Flag | terraform destroy | Deletes local files | Prompts |
127
+ |------|-------------------|---------------------|---------|
128
+ | _(none)_ | Prompted | Prompted | Yes |
129
+ | `--full` / `-y` | Always (if state exists) | Yes | No |
130
+ | `--force` / `-f` | **Skipped** (leaves cloud resources) | Yes | No |
131
+ | `--skip-terraform` | Never | Yes | Final delete only |
132
+
133
+ > `--force` is for the "just clean up my local files" case — it intentionally
134
+ > leaves cloud infrastructure running. Reach for `--full` when you want the
135
+ > environment fully gone.
136
+
109
137
  ## Plugin Generators
110
138
 
111
139
  Gems that follow the Belt plugin contract are auto-discovered:
data/lib/belt/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Belt
4
- VERSION = '0.3.31'
4
+ VERSION = '0.3.33'
5
5
  end
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.31
4
+ version: 0.3.33
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stowzilla