Skip to content

Severless does not seem to handle using a shared api gateway v2 defined in the same CF stack #13178

@maciejewiczow

Description

@maciejewiczow

Issue description

I am trying to share an API Gateway V2 between all the functions in the serverless file. When I tried using provider.httpApi.apiId["Fn::ImportValue"] with an export name defined in the same serverless.yml in resources.Resources.Outputs it failed with an error saying that it cannot resolve the export. When I modified the config to be as in the issue context, the deployment succeeds, CF stack is marked as CREATE_COMPLETE or UPDATE_COMPLETE in the AWS console, but serverless deploy fails with an error seen in the issue context. This is pretty annoying because I have to swallow this error in my github actions pipeline, and also the command does not print the stack outputs when it errors out, which is another problem I need to solve now.

Also the command serverless deploy prints the following warning at the beginning when ran:

[!] Invalid configuration encountered
  at 'provider.httpApi.id': must have required property 'Fn::ImportValue'
  at 'provider.httpApi.id': unrecognized property 'Ref'

Context

Service Overview

  • Serverless Framework Version: 4.27.0
  • Service Config File: serverless.yml
  • Service Name: recipe-scraper
  • Service App: recipe-scraper
  • Service Runtime: python3.13
  • Service Stage: prod
  • Service Region: eu-north-1
  • Error Code: UNABLE_TO_RESOLVE_HTTP_API_ID

Service Path

[REDACTED]

Command

deploy --param recipeTTL=5d,recipeReadyNotificationBody=Ready,recipeReadyNotificationTitle=Recipe,aiBaseUrl=https://api.openai.com/v1,promptIdPL=[REDACTED],maxAiParseRetryCount=4,promptIdEN=[REDACTED],domainName=[REDACTED],aiModelName=gpt-5-nano,--verbose true

Error Message

Could not resolve provider.httpApi.id parameter. Expected params.ApiId to be a string

Error Stacktrace

at file:///[REDACTED - windows user docs dir]/.serverless/releases/4.27.0/package/dist/sf-core.js:1117:3845
at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
at async Promise.all (index 1)
at async aws:info:gatherData (file:///[REDACTED - windows user docs dir]/.serverless/releases/4.27.0/package/dist/sf-core.js:1117:12599)
at async PluginManager.runHooks (file:///[REDACTED - windows user docs dir]/.serverless/releases/4.27.0/package/dist/sf-core.js:1372:11105)
at async PluginManager.invoke (file:///[REDACTED - windows user docs dir]/.serverless/releases/4.27.0/package/dist/sf-core.js:1372:11874)
at async PluginManager.spawn (file:///[REDACTED - windows user docs dir]/.serverless/releases/4.27.0/package/dist/sf-core.js:1372:12236)
at async PluginManager.runHooks (file:///[REDACTED - windows user docs dir]/.serverless/releases/4.27.0/package/dist/sf-core.js:1372:11105)
at async PluginManager.invoke (file:///[REDACTED - windows user docs dir]/.serverless/releases/4.27.0/package/dist/sf-core.js:1372:11874)
at async PluginManager.run (file:///[REDACTED - windows user docs dir]/.serverless/releases/4.27.0/package/dist/sf-core.js:1372:12607)
at async Serverless.run (file:///[REDACTED - windows user docs dir]/.serverless/releases/4.27.0/package/dist/sf-core.js:1379:10524)
at async runFramework (file:///[REDACTED - windows user docs dir]/.serverless/releases/4.27.0/package/dist/sf-core.js:1406:1781)
at async TraditionalRunner.run (file:///[REDACTED - windows user docs dir]/.serverless/releases/4.27.0/package/dist/sf-core.js:1402:28508)
at async route (file:///[REDACTED - windows user docs dir]/.serverless/releases/4.27.0/package/dist/sf-core.js:1577:2848)
at async Object.run (file:///[REDACTED - windows user docs dir]/.serverless/releases/4.27.0/package/dist/sf-core.js:1578:3876)
at async run2 (file:///[REDACTED - windows user docs dir]/.serverless/releases/4.27.0/package/dist/sf-core.js:1578:5030)

Service Config

org: maciejcorp
app: recipe-scraper
service: recipe-scraper
provider:
  name: aws
  runtime: python3.13
  stage: prod
  region: eu-north-1
  architecture: arm64
  httpApi:
    id: !Ref Gateway
  layers:
    - !Ref SharedLambdaLayer
functions:
  # endpoint handlers
  get-recipe:
    handler: functions/get_recipe/handler.handler
    environment:
      DYNAMO_USER_QUOTA_TABLE_NAME: !ImportValue RecipeScraperPermanentResourcesProd-UserQuotaTableName
      RECIPES_TABLE_NAME: !ImportValue RecipeScraperPermanentResourcesProd-RecipesTableName
    events:
      - httpApi:
          path: /recipe/{recipeId}
          method: get
          authorizer:
            type: jwt
            id:
              Ref: ApiGatewayAuthorizer
    iam:
      inheritStatements: true
      role:
        statements:
          - Effect: Allow
            Action:
              - dynamodb:GetItem
              - dynamodb:PutItem
              - dynamodb:Query
            Resource:
              - !ImportValue RecipeScraperPermanentResourcesProd-RecipesTableArn
              - !ImportValue RecipeScraperPermanentResourcesProd-UserQuotaTableArn
  scrape-recipe:
    handler: functions/scrape_recipe/handler.handler
    events:
      - httpApi:
          path: /recipe/scrape
          method: post
          authorizer:
            type: jwt
            id:
              Ref: ApiGatewayAuthorizer
    iam:
      inheritStatements: true
      role:
        statements:
          - Effect: Allow
            Action:
              - dynamodb:GetItem
              - dynamodb:PutItem
              - dynamodb:Query
            Resource:
              - !ImportValue RecipeScraperPermanentResourcesProd-RecipesTableArn
              - !ImportValue RecipeScraperPermanentResourcesProd-UserQuotaTableArn
          - Effect: Allow
            Action:
              - states:StartExecution
            Resource: ${self:resources.Outputs.ProcessIngredientsStepFn.Value}
          - Effect: Allow
            Action:
              - sns:CreatePlatformEndpoint
            Resource: ${env:SNS_ANDROID_PLATFORM_APPLICATION_ARN}
    environment:
      DYNAMO_USER_QUOTA_TABLE_NAME: !ImportValue RecipeScraperPermanentResourcesProd-UserQuotaTableName
      RECIPES_TABLE_NAME: !ImportValue RecipeScraperPermanentResourcesProd-RecipesTableName
      PROCESS_INGREDIENTS_STEP_FN_ARN: ${self:resources.Outputs.ProcessIngredientsStepFn.Value}
      RECIPE_TTL: ${param:recipeTTL}
      SNS_PLARFORM_APPLICATION_ARN__ANDROID: ${env:SNS_ANDROID_PLATFORM_APPLICATION_ARN}
  parse-result-webhook:
    handler: functions/parse_result_webhook/handler.handler
    events:
      - httpApi:
          path: /ai/webhook
          method: post
    environment:
      DYNAMO_RESPONSES_TABLE_NAME: !ImportValue RecipeScraperPermanentResourcesProd-OpenAiResponsesTableName
      AI__API_KEY: ${env:AI_API_KEY}
      AI__BASE_URL: ${param:aiBaseUrl}
      AI__WEBHOOK_SECRET: ${env:AI_WEBHOOK_SECRET}
    iam:
      inheritStatements: true
      role:
        statements:
          - Effect: Allow
            Action:
              - dynamodb:GetItem
            Resource: !ImportValue RecipeScraperPermanentResourcesProd-OpenAiResponsesTableArn
          - Effect: Allow
            Action:
              - states:SendTaskSuccess
              - states:SendTaskFailure
            Resource: ${self:resources.Outputs.ProcessIngredientsStepFn.Value}
  # iternal processing
  assemble-recipe:
    handler: functions/assemble_recipe/handler.handler
    environment:
      RECIPES_TABLE_NAME: !ImportValue RecipeScraperPermanentResourcesProd-RecipesTableName
      NOTIFICATION__BODY: ${param:recipeReadyNotificationBody}
      NOTIFICATION__TITLE: ${param:recipeReadyNotificationTitle}
    iam:
      inheritStatements: true
      role:
        statements:
          - Effect: Allow
            Action:
              - dynamodb:GetItem
              - dynamodb:PutItem
            Resource: !ImportValue RecipeScraperPermanentResourcesProd-RecipesTableArn
          - Effect: Allow
            Action:
              - sns:Publish
              - sns:DeleteEndpoint
            Resource: ${env:SNS_ANDROID_PLATFORM_APPLICATION_ARN}
  parse-ingredient-start:
    handler: functions/parse_ingredient_start/handler.handler
    environment:
      AI__API_KEY: ${env:AI_API_KEY}
      AI__BASE_URL: ${param:aiBaseUrl}
      AI__MODEL_NAME: ${param:aiModelName}
      PROMPT_ID__PL: ${param:promptIdPL}
      PROMPT_ID__EN: ${param:promptIdEN}
      DYNAMO_RESPONSES_TABLE_NAME: !ImportValue RecipeScraperPermanentResourcesProd-OpenAiResponsesTableName
    iam:
      inheritStatements: true
      role:
        statements:
          - Effect: Allow
            Action:
              - dynamodb:PutItem
            Resource: !ImportValue RecipeScraperPermanentResourcesProd-OpenAiResponsesTableArn
  parse-ingredient-success:
    handler: functions/parse_ingredient_success/handler.handler
    environment:
      AI__API_KEY: ${env:AI_API_KEY}
      AI__BASE_URL: ${param:aiBaseUrl}
      MAX_RETRY_COUNT: ${param:maxAiParseRetryCount}
      DYNAMO_RESPONSES_TABLE_NAME: !ImportValue RecipeScraperPermanentResourcesProd-OpenAiResponsesTableName
    iam:
      inheritStatements: true
      role:
        statements:
          - Effect: Allow
            Action:
              - dynamodb:GetItem
              - dynamodb:DeleteItem
            Resource: !ImportValue RecipeScraperPermanentResourcesProd-OpenAiResponsesTableArn
  parse-ingredient-fail:
    handler: functions/parse_ingredient_fail/handler.handler
    environment:
      DYNAMO_RESPONSES_TABLE_NAME: !ImportValue RecipeScraperPermanentResourcesProd-OpenAiResponsesTableName
    iam:
      inheritStatements: true
      role:
        statements:
          - Effect: Allow
            Action:
              - dynamodb:DeleteItem
            Resource: !ImportValue RecipeScraperPermanentResourcesProd-OpenAiResponsesTableArn
  parse-ingr-prep-retry:
    handler: functions/parse_ingredient_prepare_for_retry/handler.handler
    environment:
      DYNAMO_RESPONSES_TABLE_NAME: !ImportValue RecipeScraperPermanentResourcesProd-OpenAiResponsesTableName
    iam:
      inheritStatements: true
      role:
        statements:
          - Effect: Allow
            Action:
              - dynamodb:GetItem
            Resource: !ImportValue RecipeScraperPermanentResourcesProd-OpenAiResponsesTableArn
# used within processIngredients state machine definition file
custom:
  parseIngredientStartFnName: !Ref ParseDashingredientDashstartLambdaFunction
  parseIngredientFailFnName: !Ref ParseDashingredientDashfailLambdaFunction
  parseIngredientSuccessFnName: !Ref ParseDashingredientDashsuccessLambdaFunction
  assembleRecipeFnName: !Ref AssembleDashrecipeLambdaFunction
  parseIngredientFailNotificationSNSTopic: !ImportValue RecipeScraperPermanentResourcesProd-OutOfCreditsAdminNotificationsTopic
  prepareForRetryFunctionArn: !Ref ParseDashingrDashprepDashretryLambdaFunction
  pythonRequirements:
    useUv: true
    dockerizePip: non-linux
  customDomain:
    domainName: ${param:domainName}
    stage: prod
    basePath: prod
    createRoute53Record: false
    createRoute53IPv6Record: false
    endpointType: REGIONAL
    apiType: http
    autoDomain: true
    certificateArn: ${env:DOMAIN_CERTIFICATE_ARN}
stepFunctions:
  stateMachines:
    processIngredients:
      name: ProcessIngredients
      definition: ${file(functions/process_ingredients/stateMachine.asl.yml)}
layers:
  shared:
    path: lib
    compatibleArchitectures:
      - arm64
    compatibleRuntimes:
      - python3.13
resources:
  Description: "CloudFormation functions template for ${self:service}"
  Resources:
    Gateway:
      Type: AWS::ApiGatewayV2::Api
      Properties:
        Name: Gateway
        ProtocolType: HTTP
        DisableExecuteApiEndpoint: true
      DeletionPolicy: Retain
      UpdateReplacePolicy: Retain
    GatewayStage:
      Type: AWS::ApiGatewayV2::Stage
      Properties:
        ApiId: !Ref Gateway
        StageName: prod
        AutoDeploy: true
    GatewayDomainMapping:
      Type: AWS::ApiGatewayV2::ApiMapping
      Properties:
        ApiId: !Ref Gateway
        DomainName: ${param:domainName}
        Stage: prod
      DependsOn: GatewayStage
    ApiGatewayAuthorizer:
      Type: AWS::ApiGatewayV2::Authorizer
      Properties:
        AuthorizerResultTtlInSeconds: 0
        IdentitySource:
          - $request.header.Authorization
        Name: cognito-authorizer
        ApiId: !Ref Gateway
        AuthorizerType: JWT
        JwtConfiguration:
          Audience:
            - Fn::ImportValue: RecipeScraperCognitoProd-UserPoolClient
          Issuer:
            Fn::Join:
              - ""
              - - "https://cognito-idp."
                - "${opt:region, self:provider.region}"
                - ".amazonaws.com/"
                - Fn::ImportValue: RecipeScraperCognitoProd-UserPool
  Outputs:
    ProcessIngredientsStepFn:
      Value: !Ref ProcessIngredients
    Gateway:
      Value: !Ref Gateway
      Export:
        Name: !Sub "${AWS::StackName}-Gateway"
    ApiEndpoint:
      Value: !GetAtt Gateway.ApiEndpoint
      Export:
        Name: !Sub "${AWS::StackName}-ApiEndpoint"
plugins:
  - serverless-step-functions
  - serverless-domain-manager

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions