belt 0.3.23 → 0.3.25

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: 1e5abc1ab2820331aaad684f79c2b43cd5f5d99d0c38abbbd38e4dcd1efe0aad
4
- data.tar.gz: 315d3cf0a8209683598ba624e5a5017b26d764b0e1e88ab654cdaa7eedfe7ab9
3
+ metadata.gz: 5f40dfd0e0c761eeecce4896ee9c8f55ac9a97e98dd734d3c12e713a110d65a1
4
+ data.tar.gz: 88ef3eea4944d9edac97929f83e892fb4e204fcab15c7cdbace48356e4cb0cab
5
5
  SHA512:
6
- metadata.gz: 2a0254ea0e2b6de26b6cf3e65caee76db2df44a714a492edfeef0490e2644fa6bdf2a38c6150e09c71d94d389f699d60fcfd0235be8a44f4da25bc28a0cfba17
7
- data.tar.gz: 7310bc92ea3749017da52f6be928288b3970c5e52a828d948796f44e75a2ff628652db767227622f4e371889c37a7387d2aca02a844fae32c108b95448a4a603
6
+ metadata.gz: c84e237feafd376b1064d624d0d6bf89bd2409363eb660e2f3c03c1d35a5023b98fcee81b1d826dc8ebcb7e5a0a3fe18c1635006ec771be7d514c5d6e051a546
7
+ data.tar.gz: 9d342d91da2029541c4f238e36f055bcbf4118c6d8087c00be5defa5b6e0d9b66e8c5f24b73e69e52a983acc7363f020ed6b6b538499e2bbe4c19e903da9ac72
@@ -5,6 +5,7 @@ require 'erb'
5
5
  require 'json'
6
6
  require_relative 'app_detection'
7
7
  require_relative 'environment_config'
8
+ require_relative 'setup_command'
8
9
 
9
10
  module Belt
10
11
  module CLI
@@ -100,6 +101,9 @@ module Belt
100
101
  # Resolve state bucket using the specified profile (or current credentials)
101
102
  @state_bucket = resolve_state_bucket_for_profile(@aws_profile)
102
103
 
104
+ # Ensure the state bucket exists (convention over configuration)
105
+ ensure_state_bucket_exists(@state_bucket)
106
+
103
107
  puts 'Creating dns infrastructure...' unless @quiet
104
108
  if @aws_profile && !@quiet
105
109
  puts " Using AWS profile: #{@aws_profile}"
@@ -346,6 +350,26 @@ module Belt
346
350
 
347
351
  "belt-terraform-state-#{data['Account']}"
348
352
  end
353
+
354
+ # Ensure the state bucket exists, creating it if necessary.
355
+ # Uses belt setup state --aws-profile for convention over configuration.
356
+ def ensure_state_bucket_exists(bucket)
357
+ return if bucket == 'belt-terraform-state' # Placeholder means no credentials
358
+ return if bucket_exists?(bucket)
359
+
360
+ puts "State bucket #{bucket} not found. Creating it..." unless @quiet
361
+ args = []
362
+ args += ['--aws-profile', @aws_profile] if @aws_profile
363
+ SetupCommand.new(args, quiet: @quiet, aws_profile: @aws_profile).run_state_setup
364
+ end
365
+
366
+ def bucket_exists?(bucket)
367
+ require 'open3'
368
+ cmd = ['aws', 's3api', 'head-bucket', '--bucket', bucket]
369
+ cmd += ['--profile', @aws_profile] if @aws_profile
370
+ _, status = Open3.capture2e(*cmd)
371
+ status.success?
372
+ end
349
373
  end
350
374
  end
351
375
  end
@@ -29,26 +29,48 @@ module Belt
29
29
  when 'frontend'
30
30
  Belt::CLI::FrontendSetupCommand.run(args)
31
31
  else
32
- puts 'Usage: belt setup <state|tables|frontend> [options]'
33
- puts "\nSubcommands:"
34
- puts ' state Set up S3 bucket for Terraform state'
35
- puts ' tables Generate DynamoDB table definitions from contracts.rb'
36
- puts ' frontend Generate S3 + CloudFront infrastructure for frontend hosting'
32
+ print_usage
37
33
  exit 1
38
34
  end
39
35
  end
40
36
 
41
- def initialize(args = [], quiet: false)
37
+ def self.print_usage
38
+ puts <<~USAGE
39
+ Usage: belt setup <state|tables|frontend> [options]
40
+
41
+ Subcommands:
42
+ state Set up S3 bucket for Terraform state
43
+ tables Generate DynamoDB table definitions from contracts.rb
44
+ frontend Generate S3 + CloudFront infrastructure for frontend hosting
45
+
46
+ Options for state:
47
+ --aws-profile NAME Use a specific AWS profile (for multi-account setups)
48
+ --bucket NAME Use a custom bucket name instead of belt-terraform-state-<account>
49
+ --select Interactively select from existing buckets
50
+
51
+ Examples:
52
+ belt setup state # Create/verify bucket using current credentials
53
+ belt setup state --aws-profile fpshared # Create bucket in a specific AWS account
54
+ belt setup state --select # Choose from existing buckets
55
+ belt setup state --bucket my-bucket # Use a custom bucket name
56
+
57
+ The state bucket stores Terraform state for all Belt applications in an AWS account.
58
+ By default, Belt uses `belt-terraform-state-<account_id>` as the bucket name.
59
+ USAGE
60
+ end
61
+
62
+ def initialize(args = [], quiet: false, aws_profile: nil)
42
63
  @app_name = detect_app_name
43
64
  @env_name = nil
44
65
  @custom_bucket = nil
45
66
  @select_mode = false
46
67
  @quiet = quiet
68
+ @aws_profile = aws_profile
47
69
 
48
70
  parse_args(args)
49
71
 
50
72
  @region = detect_region
51
- @bucket_name = resolve_bucket_name
73
+ @bucket_name = nil # Resolved after credentials check
52
74
  end
53
75
 
54
76
  def run_state_setup
@@ -62,14 +84,21 @@ module Belt
62
84
  exit 1
63
85
  end
64
86
 
87
+ # Show which profile we're using
88
+ puts "Using AWS profile: #{@aws_profile}" if @aws_profile && !@quiet
89
+
65
90
  # Resolve final bucket name now that we have credentials
66
- @bucket_name = resolve_bucket_name unless @custom_bucket
91
+ @bucket_name = @custom_bucket || resolve_bucket_name
67
92
 
68
93
  @bucket_name = interactive_bucket_selection if @select_mode
69
94
  setup_or_verify_bucket
70
95
  apply_lifecycle(@bucket_name)
71
96
  say ' ensure lifecycle rules (90-day noncurrent expiration)'
72
- update_backend_config
97
+
98
+ # Only update backend configs when we know the target scope.
99
+ # If --aws-profile is set but no env_name, we're creating a bucket for a specific
100
+ # account (e.g., DNS infrastructure) — don't touch other environments' backends.
101
+ update_backend_config unless @aws_profile && @env_name.nil?
73
102
  print_success_message
74
103
  end
75
104
 
@@ -140,10 +169,14 @@ module Belt
140
169
  when '--bucket'
141
170
  @custom_bucket = args.shift
142
171
  abort '✗ --bucket requires a value' unless @custom_bucket
172
+ when '--aws-profile'
173
+ @aws_profile = args.shift
174
+ abort '✗ --aws-profile requires a value' unless @aws_profile
143
175
  when '--select'
144
176
  @select_mode = true
145
177
  when '--help', '-h'
146
- self.class.run([])
178
+ self.class.print_usage
179
+ exit 0
147
180
  else
148
181
  @env_name = arg unless arg.start_with?('-')
149
182
  end
@@ -194,7 +227,9 @@ module Belt
194
227
  end
195
228
 
196
229
  def list_buckets
197
- output = safe_capture('aws', 's3api', 'list-buckets', '--query', 'Buckets[].Name', '--output', 'json')
230
+ cmd = ['aws', 's3api', 'list-buckets', '--query', 'Buckets[].Name', '--output', 'json']
231
+ cmd += ['--profile', @aws_profile] if @aws_profile
232
+ output = safe_capture(*cmd)
198
233
  return [] unless output
199
234
 
200
235
  JSON.parse(output)
@@ -205,7 +240,9 @@ module Belt
205
240
  # --- AWS operations ---
206
241
 
207
242
  def aws_configured?
208
- output, status = Open3.capture2e('aws', 'sts', 'get-caller-identity')
243
+ cmd = %w[aws sts get-caller-identity]
244
+ cmd += ['--profile', @aws_profile] if @aws_profile
245
+ output, status = Open3.capture2e(*cmd)
209
246
  if status.success?
210
247
  data = begin
211
248
  JSON.parse(output)
@@ -223,7 +260,9 @@ module Belt
223
260
  attr_reader :aws_account_id
224
261
 
225
262
  def bucket_exists?(bucket)
226
- output, status = Open3.capture2e('aws', 's3api', 'head-bucket', '--bucket', bucket)
263
+ cmd = ['aws', 's3api', 'head-bucket', '--bucket', bucket]
264
+ cmd += ['--profile', @aws_profile] if @aws_profile
265
+ output, status = Open3.capture2e(*cmd)
227
266
  return true if status.success?
228
267
 
229
268
  # 403 = bucket exists but owned by someone else; 404 = doesn't exist
@@ -239,6 +278,7 @@ module Belt
239
278
  def create_bucket(bucket)
240
279
  args = ['aws', 's3api', 'create-bucket', '--bucket', bucket, '--region', @region]
241
280
  args.push('--create-bucket-configuration', "LocationConstraint=#{@region}") unless @region == 'us-east-1'
281
+ args += ['--profile', @aws_profile] if @aws_profile
242
282
  run!(*args)
243
283
  end
244
284
 
@@ -252,11 +292,14 @@ module Belt
252
292
  AbortIncompleteMultipartUpload: { DaysAfterInitiation: 7 }
253
293
  }]
254
294
  }
255
- run!('aws', 's3api', 'put-bucket-lifecycle-configuration', '--bucket', bucket,
256
- '--lifecycle-configuration', JSON.generate(lifecycle))
295
+ cmd = ['aws', 's3api', 'put-bucket-lifecycle-configuration', '--bucket', bucket,
296
+ '--lifecycle-configuration', JSON.generate(lifecycle)]
297
+ cmd += ['--profile', @aws_profile] if @aws_profile
298
+ run!(*cmd)
257
299
  end
258
300
 
259
301
  def run!(*args)
302
+ args = inject_profile(args)
260
303
  output, status = Open3.capture2e(*args)
261
304
  return if status.success?
262
305
 
@@ -265,11 +308,23 @@ module Belt
265
308
  exit 1
266
309
  end
267
310
 
268
- def safe_capture(*)
269
- output, status = Open3.capture2(*, err: File::NULL)
311
+ def safe_capture(*args)
312
+ args = inject_profile(args)
313
+ output, status = Open3.capture2(*args, err: File::NULL)
270
314
  status.success? ? output : nil
271
315
  end
272
316
 
317
+ # Inject --profile flag for AWS CLI commands if @aws_profile is set
318
+ def inject_profile(args)
319
+ return args unless @aws_profile
320
+ return args unless args.first == 'aws'
321
+
322
+ # Already has a profile flag
323
+ return args if args.include?('--profile')
324
+
325
+ args + ['--profile', @aws_profile]
326
+ end
327
+
273
328
  def say(message)
274
329
  puts message unless @quiet
275
330
  end
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.23'
4
+ VERSION = '0.3.25'
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.23
4
+ version: 0.3.25
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stowzilla