rubyonjets/jets

Missing API Methods leads to 500 errors

Closed this issue ยท 2 comments

I am not 100% sure if this is an config issue on my side.

Checklist

  • [ ๐Ÿ‘ ] Upgrade Jets: Are you using the latest version of Jets? This allows Jets to fix issues fast. There's a jets upgrade command that makes this a simple task. There's also an Upgrading Guide: http://rubyonjets.com/docs/upgrading/
  • [๐Ÿ‘ ] Reproducibility: Are you reporting a bug others will be able to reproduce and not asking a question. If you're unsure or want to ask a question, do so on https://community.boltops.com
  • Code sample: Have you put together a code sample to reproduce the issue and make it available? Code samples help speed up fixes dramatically. If it's an easily reproducible issue, then code samples are not needed. If you're unsure, please include a code sample.

My Environment

Software Version
Operating System OSX 14.0
Jets 5.0.8
Ruby 3.2.2

Expected Behaviour

Routes map to controller lambda via lambda proxy

Current Behavior

I have created a brand new jets project on 5.0.8. I have a few different controller methods using GET/PUT/POST,etc. When deploying, only a get catachall seems to be created on API gateway

Step-by-step reproduction instructions

Create a clean jets 5.0.8 project, add a model and crud methods, deploy.

Code Sample

Output from jets routes:

+--------------------+---------+-------------------------------------+--------------------------+
|    As (Prefix)     |  Verb   |         Path (URI Pattern)          |    Controller#action     |
+--------------------+---------+-------------------------------------+--------------------------+
| health             | GET     | /health                             | health#health            |
| v1_sources         | GET     | /v1/sources                         | v1/sources#index         |
| v1_sources         | POST    | /v1/sources                         | v1/sources#create        |
| v1_source          | GET     | /v1/sources/:id                     | v1/sources#show          |
| v1_source          | PUT     | /v1/sources/:id                     | v1/sources#update        |
| v1_source          | PATCH   | /v1/sources/:id                     | v1/sources#update        |
| v1_source_channels | GET     | /v1/sources/:source_id/channels     | v1/channels#index        |
| v1_source_channels | POST    | /v1/sources/:source_id/channels     | v1/channels#create       |
| v1_source_channel  | GET     | /v1/sources/:source_id/channels/:id | v1/channels#show         |
| v1_source_channel  | PUT     | /v1/sources/:source_id/channels/:id | v1/channels#update       |
| v1_source_channel  | PATCH   | /v1/sources/:source_id/channels/:id | v1/channels#update       |
| v1_account_sources | GET     | /v1/account_sources                 | v1/account_sources#index |
| v1_account_source  | GET     | /v1/account_sources/:id             | v1/account_sources#show  |
| v1_insights        | GET     | /v1/insights                        | v1/insights#index        |
|                    | OPTIONS | /*catchall                          | cors#preflight           |
+--------------------+---------+-------------------------------------+--------------------------+

API Gateway Resources:

/
  GET
  /{+catchall}

ApiMethod template:

Resources:
  AnyCatchallApiMethod:
    Type: AWS::ApiGateway::Method
    Properties:
      ResourceId: !Ref CatchallApiResource
      RestApiId: !Ref RestApi
      HttpMethod: GET
      RequestParameters: {}
      AuthorizationType: NONE
      ApiKeyRequired: 'false'
      Integration:
        IntegrationHttpMethod: POST
        Type: AWS_PROXY
        Uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${JetsControllerLambdaFunction}/invocations
      MethodResponses: []
Parameters:
  RestApi:
    Type: String
  JetsControllerLambdaFunction:
    Type: String
  CatchallApiResource:
    Type: String

I have found the culprit, I have a custom catchall method in my routes for options as this endpoint is doing some custom cors stuff:

def ensure_one_apigw_method_proxy_routes!
      return unless Jets.config.cfn.build.routes == "one_apigw_method_for_all_routes"

      # find before modifications
      catchall_route = Jets::Router.routes.find { |route| route.path =~ /^\/\*/ }
      root_route = Jets::Router.routes.find { |route| route.http_method == "GET" && route.path == "/" }

      # modifications
      unless catchall_route
        # Note: catchall to route does not matter. In one_apigw_method_for_all_routes mode it all goes to one lambda function
        # and then gets routed by config/routes.rb
        Jets::Router.routes << Jets::Router::Route.new(path: '/*catchall', http_method: 'ANY', to: 'jets/public#show')
      end
      if !root_route && catchall_route
        Jets::Router.routes << Jets::Router::Route.new(path: '/', http_method: 'GET', to: catchall_route.to)
      end
    end

This creates a get catchall if any route method catchall exist. even in my situation where the catchall is only for options.

I can make a PR to update this, but want to make sure I am not missing anything first.

Current monkey patch that seems to be working:

When I get a few moments of free time ill create a MR

module Jets::Cfn::Builder::Api
  class Methods < Paged
  
     ...

    def ensure_one_apigw_method_proxy_routes!
      return unless Jets.config.cfn.build.routes == "one_apigw_method_for_all_routes"

      # find before modifications
      catchall_routes = Jets::Router.routes.select { |route| route.path =~ /^\/\*/ }
      ensure_root_route!(catchall_routes)

      # find after modifications
      # only add catchall route if it does not exist for the any method.
      unless catchall_routes.detect { |route| route.http_method == "ANY" }.present?
        # Note: catchall to route does not matter. In one_apigw_method_for_all_routes mode it all goes to one lambda function
        # and then gets routed by config/routes.rb
        Jets::Router.routes << Jets::Router::Route.new(path: '/*catchall', http_method: 'ANY', to: 'jets/public#show')
      end
    end

    # If the root route is missing, add it.  This is how it works locally.
    # The GET catchall route should take precedence over the ANY catchall route.
    def ensure_root_route!(catchall_routes = [])
      root_route = Jets::Router.routes.find { |route| route.http_method == "GET" && route.path == "/" }
      return if root_route || catchall_routes.empty?

      root_catchall = catchall_routes.detect { |route| route.http_method == "GET" }
      root_catchall = catchall_routes.detect { |route| route.http_method == "GET" } unless root_catchall
      return unless root_catchall

      Jets::Router.routes << Jets::Router::Route.new(path: '/', http_method: 'GET', to: root_catchall.to)
    end
  end
end