AWS SAM で Amazon API Gateway (REST) の API Key と Usage Plan を構築するとき,実行時に以下のように API Stage not found
というエラーが出ることがある.これはよくハマる問題で,AWS SAM 関連の GitHub を見ると issue も出ている.今回は解決策と注意点を紹介しようと思う👌
Resource handler returned message: "API Stage not found: 22efb50tt2:v1 (Service: ApiGateway, Status Code: 404, Request ID: 2fe0c9bf-6d15-4438-b379-52c40bb66a80)" (RequestToken: d4727ca1-4d1c-6c68-0cda-478e6d499bd6, HandlerErrorCode: NotFound)
TL;DR
最初に解決策を書いておく❗️AWS SAM テンプレートの AWS::ApiGateway::UsagePlan
に Amazon API Gateway Stage に対する DependsOn
を追加して,Amazon API Gateway Stage の構築完了を明示的に待つ必要がある.
論理名は2種類ある!?
以下の例では DependsOn
に指定している論理名は ApiGatewayv1Stage
になっているけど,これは <api‑LogicalId><stage‑name>Stage
という命名規則に従っている📝なお,正確には以下のように Stage 名の指定によって論理名も変わってしまうため注意する🌀
<api‑LogicalId><stage‑name>Stage
(Stage 名をベタ書きする場合)<api‑LogicalId>Stage
(Stage 名を Parameters から取得する場合)
AWSTemplateFormatVersion: 2010-09-09 Transform: AWS::Serverless-2016-10-31 Resources: ApiGateway: Type: AWS::Serverless::Api Properties: Name: sandbox-api-gateway StageName: v1 DefinitionUri: ./openapi/openapi.yaml ApiGatewayKey: Type: AWS::ApiGateway::ApiKey ApiGatewayUsagePlan: Type: AWS::ApiGateway::UsagePlan Properties: ApiStages: - ApiId: !Ref ApiGateway Stage: v1 DependsOn: - ApiGatewayv1Stage ApiGatewayUsagePlanKey: Type: AWS::ApiGateway::UsagePlanKey Properties: KeyId: !Ref ApiGatewayKey KeyType: API_KEY UsagePlanId: !Ref ApiGatewayUsagePlan
検証(Stage 名をベタ書きする場合)
以下のように AWS SAM で Amazon API Gateway (REST) 関連リソースを構築する.今回 Stage 名 v1
は2箇所にベタ書きしてある.このままデプロイすると API Stage not found
というエラーが出ることがある.
AWS::Serverless::Api
AWS::ApiGateway::ApiKey
AWS::ApiGateway::UsagePlan
AWS::ApiGateway::UsagePlanKey
AWSTemplateFormatVersion: 2010-09-09 Transform: AWS::Serverless-2016-10-31 Resources: ApiGateway: Type: AWS::Serverless::Api Properties: Name: sandbox-api-gateway StageName: v1 DefinitionUri: ./openapi/openapi.yaml ApiGatewayKey: Type: AWS::ApiGateway::ApiKey ApiGatewayUsagePlan: Type: AWS::ApiGateway::UsagePlan Properties: ApiStages: - ApiId: !Ref ApiGateway Stage: v1 ApiGatewayUsagePlanKey: Type: AWS::ApiGateway::UsagePlanKey Properties: KeyId: !Ref ApiGatewayKey KeyType: API_KEY UsagePlanId: !Ref ApiGatewayUsagePlan
実は AWS SAM で AWS::Serverless::Api
を使って Amazon API Gateway (REST) を構築しようとすると,内部的に AWS::ApiGateway::Stage
と AWS::ApiGateway::Deployment
も構築される.ブラックボックスな仕様ではなく,ちゃんと以下のドキュメントに載っている.
そして,AWS::ApiGateway::UsagePlan
を構築するときに,依存する AWS::ApiGateway::Stage
の構築が間に合わず API Stage not found
というエラーが出てしまう.よって,内部的に構築される AWS::ApiGateway::Stage
の論理名の命名規則に従って <api‑LogicalId><stage‑name>Stage
に対する DependsOn
を追加する必要がある.以下の AWS SAM テンプレートは期待した通りに構築できる❗️
AWSTemplateFormatVersion: 2010-09-09 Transform: AWS::Serverless-2016-10-31 Resources: ApiGateway: Type: AWS::Serverless::Api Properties: Name: sandbox-api-gateway StageName: v1 DefinitionUri: ./openapi/openapi.yaml ApiGatewayKey: Type: AWS::ApiGateway::ApiKey ApiGatewayUsagePlan: Type: AWS::ApiGateway::UsagePlan Properties: ApiStages: - ApiId: !Ref ApiGateway Stage: v1 DependsOn: - ApiGatewayv1Stage ApiGatewayUsagePlanKey: Type: AWS::ApiGateway::UsagePlanKey Properties: KeyId: !Ref ApiGatewayKey KeyType: API_KEY UsagePlanId: !Ref ApiGatewayUsagePlan
実際に aws cloudformation describe-stack-resources
コマンドで Amazon API Gateway Stage を確認すると,確かに論理名 ApiGatewayv1Stage
で構築されている🙂
$ aws cloudformation describe-stack-resources --stack-name kakakakakku-blog-apigw-apikey | jq '.StackResources[] | select(.ResourceType == "AWS::ApiGateway::Stage")' { "StackName": "kakakakakku-blog-apigw-apikey", "StackId": "arn:aws:cloudformation:ap-northeast-1:000000000000:stack/kakakakakku-blog-apigw-apikey/082450d0-de49-11ed-a0dc-0acc4753a47f", "LogicalResourceId": "ApiGatewayv1Stage", "PhysicalResourceId": "v1", "ResourceType": "AWS::ApiGateway::Stage", "Timestamp": "2023-04-19T00:00:00.000000+00:00", "ResourceStatus": "CREATE_COMPLETE", "DriftInformation": { "StackResourceDriftStatus": "NOT_CHECKED" } }
検証(Stage 名を Parameters から取得する場合)
しかし Stage 名 v1
をベタ書きするのではなく,AWS CloudFormation の Parameters から取得しようとすると,先ほど紹介した AWS::ApiGateway::Stage
の論理名の命名規則が <api‑LogicalId>Stage
に変わってしまうという注意点がある(ドキュメントにも書かれていないように思う...💧).よって,以下のように論理名 ApiGatewayStage
に対する DependsOn
を追加すると AWS SAM テンプレートは期待した通りに構築できる❗️
AWSTemplateFormatVersion: 2010-09-09 Transform: AWS::Serverless-2016-10-31 Parameters: StageName: Type: String Default: v1 Resources: ApiGateway: Type: AWS::Serverless::Api Properties: Name: sandbox-api-gateway StageName: !Ref StageName DefinitionUri: ./openapi/openapi.yaml ApiGatewayKey: Type: AWS::ApiGateway::ApiKey ApiGatewayUsagePlan: Type: AWS::ApiGateway::UsagePlan Properties: ApiStages: - ApiId: !Ref ApiGateway Stage: !Ref StageName DependsOn: - ApiGatewayStage ApiGatewayUsagePlanKey: Type: AWS::ApiGateway::UsagePlanKey Properties: KeyId: !Ref ApiGatewayKey KeyType: API_KEY UsagePlanId: !Ref ApiGatewayUsagePlan
もう一度 aws cloudformation describe-stack-resources
コマンドで Amazon API Gateway Stage を確認すると,確かに論理名は ApiGatewayStage
で構築されている🙂
$ aws cloudformation describe-stack-resources --stack-name kakakakakku-blog-apigw-apikey | jq '.StackResources[] | select(.ResourceType == "AWS::ApiGateway::Stage")' { "StackName": "kakakakakku-blog-apigw-apikey", "StackId": "arn:aws:cloudformation:ap-northeast-1:000000000000:stack/kakakakakku-blog-apigw-apikey/26030900-de4b-11ed-ab51-0e3a83b37d3b", "LogicalResourceId": "ApiGatewayStage", "PhysicalResourceId": "v1", "ResourceType": "AWS::ApiGateway::Stage", "Timestamp": "2023-04-19T00:00:00.000000+00:00", "ResourceStatus": "CREATE_COMPLETE", "DriftInformation": { "StackResourceDriftStatus": "NOT_CHECKED" } }
まとめ
AWS SAM で Amazon API Gateway (REST) の API Key と Usage Plan を構築するときに出る可能性のある API Stage not found
というエラーに関してまとめてみた.論理名の命名規則が2種類あるところは注意❗️
参考になれば💡
おまけ
せっかく AWS SAM で AWS::Serverless::Api
を使ってるなら Auth
プロパティを使って API Key と Usage Plan を構築すればもっと簡単じゃん❓という話もあると思うけど,AWS::Serverless::Api
の DefinitionUri
で OpenAPI ファイルを指定している場合には Auth
プロパティが使えないという仕様になっている💧
$ sam validate -t template.yaml Error: [InvalidResourceException('ApiGateway', "Auth works only with inline Swagger specified in 'DefinitionBody' property.")] ('ApiGateway', "Auth works only with inline Swagger specified in 'DefinitionBody' property.")