belt 0.5.3 → 0.6.1

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: 2267465d1ad5fcc7e7e8b7d7c6be91919275a8bd6bc541c82c9751ac2265ab27
4
- data.tar.gz: 12c71c5a5db0520f26ee6df68fbbbbd5403105cb9ab2c08da2719d6513a6269e
3
+ metadata.gz: 66c35b71a47f74ba7805bce24b37eb02fa6ded7246872df30a951a35be22d21f
4
+ data.tar.gz: 5ddc3bb756fdf782da5074abb72bd0c7275c6a90f4ba98c81963b92cb9cd6bb9
5
5
  SHA512:
6
- metadata.gz: 7716c4d8eebea928388accc998fb38f00ffedf9c5cd8585ef6e8540d2a524e5a139002d434b1a5644e3b656c394692438715b39e87d7e9682b05b948273a8213
7
- data.tar.gz: 7259396f97e7e750c9b6f19092535deb3552e98a2d9af927f01a0bfef09352b0164ad9febf1de4572ca132ad82686d60f101c5b640a32013d88019395e0dd70a
6
+ metadata.gz: 44320ad96e29e29a6bb9b88d2de94822f982dad01e92bc639204c38d2bb7a306463b7b570ac8c35cbf13cc00a223a55442b52171ac64f3357de98a9662a5e3e9
7
+ data.tar.gz: 8e176982bec58892623685d2b3bfd02cde1ab22d9fcac2c6eb1bfe58f5ae27f02d28b708cced560b803379b36bcd2bea5463fce749bde4ebd9b169cbfff878ba
data/CHANGELOG.md CHANGED
@@ -1,5 +1,43 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.1
4
+
5
+ ### Fixed
6
+
7
+ - **`before_action` now halts the request when it produces a response.** The docs said
8
+ a callback could halt by calling `error_response`, but `dispatch` ignored callback
9
+ output and ran the action anyway, so the action's response replaced the error. A
10
+ guard like `error_response('Forbidden', 403) unless admin?` let everyone through.
11
+ Now, once a callback calls a response helper (`error_response`, `success_response`,
12
+ `html_response`, `head`, `render`) or returns a response, the remaining callbacks, the
13
+ action and the `after_action`s are skipped and that response is returned
14
+ (Rails-style). Apps that worked around this with inline guards in each action keep
15
+ working; the inline checks are now redundant.
16
+
17
+ ## 0.6.0
18
+
19
+ ### Added
20
+
21
+ - **Ruby 4.0.** New apps and environments now run on the `ruby4.0` Lambda runtime.
22
+ The generated `infrastructure/<env>/main.tf` sets `ruby_version = "4.0"` on the
23
+ conveyor-belt provider (one setting for runtime + gem build image), and both
24
+ generated `main.tf` files now pin conveyor-belt `~> 0.0.16`, the first release that
25
+ migrates already-deployed functions when `ruby_version` changes. Belt, activeitem,
26
+ lambda_loadout, belt-pay and belt-messaging all pass their suites on Ruby 4.0. CI now
27
+ tests 3.3, 3.4 and 4.0. Existing apps: see [UPGRADING.md](UPGRADING.md#ruby-34--40).
28
+
29
+ ### Fixed
30
+
31
+ - **`belt deploy <env> --rebuild` builds gems for the runtime the function actually
32
+ runs.** It used to look for `ruby_version` in `terraform state show conveyor_belt.main`,
33
+ but that's a provider setting and never appears in resource state, so `--rebuild`
34
+ always compiled in `build-ruby3.4`. On a `ruby4.0` function that ships a
35
+ `vendor/bundle/ruby/3.4.0` bundle Ruby 4 can't load. Image selection is now: resource
36
+ `docker_build_image` → the live function's runtime → `ruby_version` in the env's
37
+ `main.tf` → `build-ruby3.4`.
38
+ - Removed `runtime: ruby3.3` from the `config/lambda/*.yml` examples in the docs.
39
+ conveyor-belt ignores that key; the runtime comes from the provider's `ruby_version`.
40
+
3
41
  ## 0.5.3
4
42
 
5
43
  ### Fixed
@@ -88,9 +88,7 @@ module Belt
88
88
  false
89
89
  end
90
90
 
91
- # before_action guard. Raises, because Belt discards before_action return values
92
- # (see BeltController::Base#run_before_actions) — returning a response hash would
93
- # let the action run anyway. Belt maps the error to 401.
91
+ # before_action guard. Raises NotAuthenticated, which Belt maps to 401.
94
92
  def authenticate_user!
95
93
  raise NotAuthenticated, 'Authentication required' unless user_signed_in?
96
94
  end
@@ -490,7 +490,7 @@ module Belt
490
490
  begin
491
491
  puts ' 📦 Building Lambda package...'
492
492
  assemble_package(lambda_dir, build_dir)
493
- build_gems(build_dir)
493
+ build_gems(build_dir, function_name)
494
494
  zip_file = create_zip(build_dir)
495
495
 
496
496
  puts ' 🚀 Deploying to AWS...'
@@ -572,13 +572,13 @@ module Belt
572
572
  puts ''
573
573
  end
574
574
 
575
- def build_gems(build_dir)
575
+ def build_gems(build_dir, function_name)
576
576
  puts ' 🐳 Building gems in Docker (Lambda-compatible)...'
577
577
 
578
578
  uid = Process.uid
579
579
  gid = Process.gid
580
580
 
581
- image = detect_docker_build_image
581
+ image = detect_docker_build_image(function_name)
582
582
 
583
583
  docker_cmd = [
584
584
  'docker', 'run', '--rm',
@@ -606,35 +606,58 @@ module Belt
606
606
  puts ' ✅ Gems installed'
607
607
  end
608
608
 
609
- # Reads docker_build_image from the conveyor_belt Terraform resource state.
610
- # Falls back to DEFAULT_DOCKER_BUILD_IMAGE if state is unavailable or attribute not set.
611
- def detect_docker_build_image
609
+ # Picks the image gems are compiled in. Native extensions and the
610
+ # vendor/bundle/ruby/<abi> path must match the function's runtime, so:
611
+ # 1. docker_build_image on the conveyor_belt resource (fully custom)
612
+ # 2. the live function's runtime (ruby4.0 → build-ruby4.0)
613
+ # 3. ruby_version in the env's conveyor-belt provider block
614
+ # 4. DEFAULT_DOCKER_BUILD_IMAGE
615
+ def detect_docker_build_image(function_name)
612
616
  env_dir = File.join(@infra_dir, @env)
613
- return DEFAULT_DOCKER_BUILD_IMAGE unless Dir.exist?(File.join(env_dir, '.terraform'))
617
+
618
+ image = docker_build_image_from_state(env_dir)
619
+ if image
620
+ puts " 📦 Using custom build image: #{image}"
621
+ return image
622
+ end
623
+
624
+ version = ruby_version_from_lambda(function_name) || ruby_version_from_provider(env_dir)
625
+ return DEFAULT_DOCKER_BUILD_IMAGE unless version
626
+
627
+ image = "public.ecr.aws/sam/build-ruby#{version}:latest-x86_64"
628
+ puts " 📦 Using build image for Ruby #{version}: #{image}"
629
+ image
630
+ end
631
+
632
+ def docker_build_image_from_state(env_dir)
633
+ return nil unless Dir.exist?(File.join(env_dir, '.terraform'))
614
634
 
615
635
  Dir.chdir(env_dir) do
616
636
  output, status = Open3.capture2('terraform', 'state', 'show', 'conveyor_belt.main')
617
- if status.success?
618
- # Prefer explicit docker_build_image if set
619
- image_match = output.match(/docker_build_image\s*=\s*"([^"]+)"/)
620
- if image_match
621
- image = image_match[1]
622
- puts " 📦 Using custom build image: #{image}"
623
- return image
624
- end
637
+ return nil unless status.success?
625
638
 
626
- # Otherwise derive from ruby_version
627
- version_match = output.match(/ruby_version\s*=\s*"([^"]+)"/)
628
- if version_match
629
- version = version_match[1]
630
- image = "public.ecr.aws/sam/build-ruby#{version}:latest-x86_64"
631
- puts " 📦 Using build image for Ruby #{version}: #{image}"
632
- return image
633
- end
634
- end
639
+ output[/docker_build_image\s*=\s*"([^"]+)"/, 1]
635
640
  end
641
+ end
642
+
643
+ # "ruby4.0" → "4.0"
644
+ def ruby_version_from_lambda(function_name)
645
+ output, status = Open3.capture2(
646
+ 'aws', 'lambda', 'get-function-configuration',
647
+ '--function-name', function_name,
648
+ '--query', 'Runtime',
649
+ '--output', 'text'
650
+ )
651
+ return nil unless status.success?
652
+
653
+ output.strip[/\Aruby(\d+\.\d+)\z/, 1]
654
+ end
655
+
656
+ def ruby_version_from_provider(env_dir)
657
+ main_tf = File.join(env_dir, 'main.tf')
658
+ return nil unless File.exist?(main_tf)
636
659
 
637
- DEFAULT_DOCKER_BUILD_IMAGE
660
+ File.read(main_tf)[/^\s*ruby_version\s*=\s*"([^"]+)"/, 1]
638
661
  end
639
662
 
640
663
  def strip_vendor_fat(build_dir)
@@ -4,6 +4,7 @@ require 'fileutils'
4
4
  require 'erb'
5
5
  require_relative 'app_detection'
6
6
  require_relative 'environment_config'
7
+ require_relative '../version'
7
8
  require_relative 'frontend_registry'
8
9
  require_relative 'frontend_setup_command'
9
10
 
@@ -5,6 +5,7 @@ require 'erb'
5
5
  require 'json'
6
6
  require 'open3'
7
7
  require_relative 'setup_command'
8
+ require_relative '../version'
8
9
 
9
10
  module Belt
10
11
  module CLI
@@ -71,8 +71,8 @@ end
71
71
  ```
72
72
 
73
73
  `authenticate_user!` raises `Belt::Authentication::NotAuthenticated`, which Belt already
74
- maps to **401**. It has to raise: `before_action` cannot halt the chain by returning a
75
- response — return values are discarded and the action runs anyway.
74
+ maps to **401**. (Your own guards can also halt by calling a response helper, e.g.
75
+ `error_response('Forbidden', 403) unless admin?`.)
76
76
 
77
77
  | Method | |
78
78
  |---|---|
@@ -96,8 +96,19 @@ class ApplicationController < BeltController::Base
96
96
  end
97
97
  ```
98
98
 
99
- Callbacks run in definition order. `before_action` can halt the request by
100
- calling a response helper (e.g., `error_response`).
99
+ Callbacks run in definition order. A `before_action` halts the request by
100
+ calling a response helper (`error_response`, `success_response`, `head`,
101
+ `render`, …). The remaining callbacks, the action and the `after_action`s are
102
+ skipped, and that response is returned:
103
+
104
+ ```ruby
105
+ def require_admin!
106
+ error_response('Forbidden', 403) unless admin?
107
+ end
108
+ ```
109
+
110
+ Raising works too: the exception goes through `rescue_from` as usual (e.g.
111
+ `Belt::AuthenticationError` → 401).
101
112
 
102
113
  ## Strong Parameters
103
114
 
@@ -67,13 +67,26 @@ The Lambda entry point is specified in `config/lambda/<name>.yml`:
67
67
 
68
68
  ```yaml
69
69
  handler: lambda/<name>.lambda_handler
70
- runtime: ruby3.3
71
70
  timeout: 30
72
71
  memory: 256
73
72
  environment:
74
73
  ENVIRONMENT: ${var.environment}
75
74
  ```
76
75
 
76
+ The Ruby version is set once per environment, on the conveyor-belt provider in
77
+ `infrastructure/<env>/main.tf` — it drives both the Lambda runtime and the image
78
+ gems are compiled in:
79
+
80
+ ```hcl
81
+ provider "conveyor-belt" {
82
+ environment = var.environment
83
+ aws_region = var.aws_region
84
+ ruby_version = "4.0" # → runtime ruby4.0, build image build-ruby4.0
85
+ }
86
+ ```
87
+
88
+ See [UPGRADING.md](../../../UPGRADING.md#ruby-34--40) to move an existing app.
89
+
77
90
  ## Route Manifests
78
91
 
79
92
  Before deploying, generate the route manifest used at runtime:
@@ -67,7 +67,6 @@ Configure via `config/lambda/<name>.yml`:
67
67
 
68
68
  ```yaml
69
69
  handler: lambda/api.lambda_handler
70
- runtime: ruby3.3
71
70
  timeout: 30
72
71
  memory: 256
73
72
  layers:
data/lib/belt/version.rb CHANGED
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Belt
4
- VERSION = '0.5.3'
4
+ VERSION = '0.6.1'
5
+
6
+ # Ruby version new apps run on in Lambda. Rendered into the conveyor-belt
7
+ # provider block (`ruby_version`) by `belt new` / `belt generate environment`.
8
+ LAMBDA_RUBY_VERSION = '4.0'
5
9
  end
@@ -9,12 +9,15 @@ require_relative '../belt/helpers/error_logging'
9
9
  require_relative '../belt/helpers/cors_origin'
10
10
  require_relative '../belt/rendering'
11
11
  require_relative 'implicit_response'
12
+ require_relative 'callback_halting'
12
13
 
13
14
  module BeltController
14
15
  class Base
15
16
  include Belt::Helpers::Response
16
17
  include Belt::Rendering
17
18
  include ImplicitResponse
19
+ # Must come after Belt::Helpers::Response / Belt::Rendering (wraps their helpers).
20
+ include CallbackHalting
18
21
  # Cognito identity: current_user, authenticate_user!, cognito_admin?.
19
22
  # Inert unless the app declares `cognito_authenticatable` on a model.
20
23
  include Belt::Authentication::Controller
@@ -139,7 +142,11 @@ module BeltController
139
142
  raise Belt::ActionNotFound, "The action '#{action_sym}' could not be found for #{self.class.name}"
140
143
  end
141
144
 
142
- run_before_actions(action_sym)
145
+ # A before_action that produced a response halts the request (Rails-style):
146
+ # the action and after_actions don't run.
147
+ halted = run_before_actions(action_sym)
148
+ return halted if halted
149
+
143
150
  # Snapshot ivars so we only auto-serialize assigns set by the action (Rails-style)
144
151
  @__assigns_before = instance_variables
145
152
  result = send(action_sym)
@@ -178,14 +185,17 @@ module BeltController
178
185
 
179
186
  private
180
187
 
188
+ # Returns the halting response if a callback produced one, else nil.
181
189
  def run_before_actions(action_sym)
182
190
  self.class.all_before_actions.each do |callback|
183
191
  next if callback[:only] && !callback[:only].include?(action_sym)
184
192
  next if callback[:except]&.include?(action_sym)
185
193
  next if should_skip_callback?(callback[:method], action_sym)
186
194
 
187
- send(callback[:method])
195
+ response = run_halting_callback(callback[:method])
196
+ return response if response
188
197
  end
198
+ nil
189
199
  end
190
200
 
191
201
  def run_after_actions(action_sym)
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BeltController
4
+ # Rails-style halting for before_action callbacks.
5
+ #
6
+ # A before_action halts the request by producing a response. Either form works:
7
+ #
8
+ # def require_admin!
9
+ # error_response('Forbidden', 403) unless admin?
10
+ # end
11
+ #
12
+ # def require_admin!
13
+ # return if admin?
14
+ #
15
+ # error_response('Forbidden', 403)
16
+ # Belt::Observability::Logger.warn('non-admin hit admin route')
17
+ # end
18
+ #
19
+ # Once a callback halts, the remaining callbacks, the action and the
20
+ # after_actions are skipped, and that response is returned.
21
+ #
22
+ # Belt's response helpers are pure (they build and return a hash), so we
23
+ # record any response built while a callback is running. That catches the
24
+ # second form, where the response isn't the callback's return value.
25
+ # Included by BeltController::Base after the response helpers so these
26
+ # wrappers sit in front of them in the ancestor chain.
27
+ module CallbackHalting
28
+ # render builds its response via html_response, so it's covered too.
29
+ %i[success_response error_response html_response head].each do |helper|
30
+ define_method(helper) do |*args|
31
+ response = super(*args)
32
+ @__callback_response = response if @__running_callback
33
+ response
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ # Run one before_action. Returns the response it produced (halt), or nil.
40
+ # A returned response wins over one that was only built along the way.
41
+ def run_halting_callback(method_name)
42
+ @__callback_response = nil
43
+ @__running_callback = true
44
+ result = send(method_name)
45
+ lambda_response?(result) ? result : @__callback_response
46
+ ensure
47
+ @__running_callback = false
48
+ end
49
+ end
50
+ end
@@ -10,7 +10,7 @@ terraform {
10
10
  }
11
11
  conveyor-belt = {
12
12
  source = "stowzilla/conveyor-belt"
13
- version = "~> 0.0.1"
13
+ version = "~> 0.0.16"
14
14
  }
15
15
  }
16
16
  }
@@ -30,6 +30,10 @@ provider "aws" {
30
30
  provider "conveyor-belt" {
31
31
  environment = var.environment
32
32
  aws_region = var.aws_region
33
+
34
+ # Lambda runtime (ruby<version>) and the Docker image gems are compiled in.
35
+ # Changing it on a deployed environment migrates every function on the next apply.
36
+ ruby_version = "<%= Belt::LAMBDA_RUBY_VERSION %>"
33
37
  }
34
38
 
35
39
  module "app" {
@@ -13,7 +13,7 @@ terraform {
13
13
  }
14
14
  conveyor-belt = {
15
15
  source = "stowzilla/conveyor-belt"
16
- version = "~> 0.0.2"
16
+ version = "~> 0.0.16"
17
17
  }
18
18
  }
19
19
  }
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.5.3
4
+ version: 0.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stowzilla
@@ -192,6 +192,7 @@ files:
192
192
  - lib/belt/version.rb
193
193
  - lib/belt/views/welcome/show.html.erb
194
194
  - lib/belt_controller/base.rb
195
+ - lib/belt_controller/callback_halting.rb
195
196
  - lib/belt_controller/implicit_response.rb
196
197
  - lib/templates/dns/backend.tf.erb
197
198
  - lib/templates/dns/belt.rb.erb