状況説明
Parameters:
Env: # local, devなど
Type: String
DatabaseEndpoint: # sam local実行時に参照するDB接続先
Type: String
Conditions:
IsLocal: !Equals [!Ref Env, "local"] # localならtrue
Resources:
# Lambda定義
Function:
Type: AWS::Serverless::Function
Properties:
# ~~省略~~
Environment:
Variables:
DATABASE_ENDPOINT: !If
- IsLocal
- !Ref DatabaseEndpoint # IsLocalがtrueのとき参照
- !GetAtt DSQL.Endpoint # IsLocalがfalseのとき参照
# Aurora DSQL定義
Dsql:
Type: AWS::DSQL::Cluster
Properties:
DeletionProtectionEnabled: true
ローカル環境でテストするときはsamconfig.tomlからDatabaseEndpointを読み取り、dev環境では実際のDSQLクラスターのエンドポイントを参照するように設定したつもりでした。
しかしこれでsam local start-apiコマンドを実行すると、DATABASE_ENDPOINTがNULLになってしまいDBへの接続ができませんでした。
原因
原因は2つありました。
1. !GetAttは本物のリソースを参照しようとする
!GetAtt DSQL.Endpoint
この場合、実際にプロビジョニングされているDSQLのエンドポイントを見に行こうとしますが、ローカルからはアクセスできないためエラーになってしまいます。
一方で!Refは本物のリソースにアクセスできない場合はtemplate.yamlに定義されているリソース名をそのまま返すため、エラーにならないようです。
2. !Ifは最初にtrueもfalseも両方評価する
DATABASE_ENDPOINT: !If
- IsLocal
- !Ref DatabaseEndpoint
- !GetAtt DSQL.Endpoint
IsLocalがtrueなので!Ref DatabaseEndpointのみ評価するのかと思いきや、なぜかfalseの方も最初に評価してしまう仕様らしいです。
注意
「SAMやCloudFormationの仕様ではなく、SAM CLIの仕様」という意味です。
結論
このような流れでDATABASE_ENDPOINTがNULLになってしまったと思われます。
回避した方法
!Ref DatabaseEndpointのみに変更しました。
Resources:
# Lambda定義
Function:
Type: AWS::Serverless::Function
Properties:
# ~~省略~~
Environment:
Variables:
DATABASE_ENDPOINT: !Ref DatabaseEndpoint
ローカル実行のとき
samconfig.tomlのparameter_overridesで環境変数を注入します。
[default.local_start_api.parameters]
parameter_overrides = [
"Env=local",
"DatabaseEndpoint=example.dsql.ap-northeast-1.on.aws"
]
dev環境のとき
GitHub actionsでCI/CDデプロイを組んでいるので、workflow内で環境変数を注入します。
- name: Deploy
run: |
sam deploy \
--config-env ${{ vars.ENV }} \
--no-confirm-changeset \
--no-fail-on-empty-changeset \
--parameter-overrides \
Env=${{ vars.ENV }} \
DatabaseEndpoint=${{ secrets.DATABASE_ENDPOINT }}
これだとGitHubのEnvironmentsにDATABASE_ENDPOINTを設定する必要がありますが、しょうがないと割り切りました。
備考
実はRole参照にも!GetAttを使っていたのですが、ここはエラーも何も出ていませんでした。
sam localでは使わないので、Role設定は無視しているということなんでしょうね。
Resources:
# Lambda定義
Function:
Type: AWS::Serverless::Function
Properties:
# ~~省略~~
Environment:
Variables:
DATABASE_ENDPOINT: !If
- IsLocal
- !Ref DatabaseEndpoint
- !GetAtt DSQL.Endpoint
Role: !GetAtt FunctionRole.Arn # ←これ
FunctionRole:
Type: AWS::IAM::Role
Properties:
# ~~省略~~
Issue
似たような事例を報告しているissueがありました。
Bug: Unable to resolve env var when referencing FunctionUrl #4510
まだOpenではありますが、最後のコメントが2023年なので当分進展はないでしょうね・・・。