Serverless Frameworkの自動生成リソースの上書き設定と、リソースの論理名変換ルールの罠

Serverless Frameworkで自動生成されたリソースに対して設定を加えたいとき、「あれ?どうやるんだ?」となり、調べてみました。

また、Serverless Frameworkにおけるリソースの論理名の変換ルール(と罠)についてもついでに触れたりしました。


リソースの自動生成

Serverless Frameworkでは、サーバーレスアプリを開発するために色々な便利な仕組みが組まれていて、そのうちの一つが「リソースの自動生成」です。


例えば、CloudFormationでLambdaファンクションを作成する場合、CloudWatch Logsであったり場合によってはLambda Permissionであったりと、Lambda以外のものも全てテンプレートで定義して作成する必要があります。


それがServerless Frameworkでは、Lambdaなどメインのリソースの定義をするだけでそのような付随するものを勝手に生成してくれます(リソースの種類によりますが)。


疑問

そこで、「この自動生成されたリソースに設定を加えたい場合、どうすればいいのか?」という疑問が生じました。

自動生成に頼らずテンプレートに作成しようとすると名前被りや紐付けなどでエラー出ないかなとか、できれば自動で作成してくれるものは定義しないで追加で上書きしたいな、とか考えていました。


resources.extensions

とりあえずServerless Frameworkのリファレンスを漁っていたら普通にありました。

www.serverless.com


resources.extensionsのルール

使用できる属性の種類

resources.extensionsで使えるリソース属性の種類は限られているようです。

  • Condition
  • CreationPolicy
  • DeletionPolicy
  • DependsOn
  • Metadata
  • Properties
  • UpdatePolicy
  • UpdateReplacePolicy


論理名変換

これはresources.extensionsのルールではなくServerless Frameworkのルールですが、Serverless Frameworkが作成する一部のリソースの論理名は命名ルールに沿って定義・変換されます。


例えば、doSomethingという論理名のLambda::FunctionDoSomethingLambdaFunctionという論理名で作成される」というような命名変換ルールがあります。

functions:
  doSomething:
    handler: handler.doSomething


各リソースの種類によって定義ルールが違うのですが、どれも論理名で指定した名前はnormalizedFunctionNameという変数として扱われます。

その上で、Lambdaの場合は「{normalizedFunctionName}LambdaFunction」というような命名ルールになります。


上記の記載例だと、normalizedFunctionName=doSomethingです。


また、normalizedFunctionName変換には、以下の特殊な変換ルールがあります。

  • 頭文字は大文字に置き換わる
  • -, _は以下の文字に置き換わる
    • - -> Dash
    • _ -> Underscore


つまり、以下のような論理名に変換されます。

  • doSomething -> DoSomethingLambdaFunction
  • do-Something -> DoDashSomethingLambdaFunction
  • do_Something -> DoUnderscoreSomethingLambdaFunction


細かい変換ルールの詳細は公式リファレンスをご覧下さい。


使用できるテンプレートのブロックの種類

resources.extensionsは、Resourcesブロックでのみ使用可能と記載されてありました。


resources.extensionsの具体例

例えばリファレンスにあった例ですが、Lambdaを作成したときに自動作成されたCloudWatchロググループに対して有効期限をつけるような拡張をする場合、以下のようになります。

functionsブロックで作成したLambdaには自動でロググループが作成されます。

   
functions:
  write-post:
    handler: handler.writePost
    events:
      - httpApi: 'POST /api/posts/new'
 
resources:
  extensions:
    WriteDashPostLogGroup:
      Properties:
        RetentionInDays: '30'


CloudWatch Logsのロググループにおける命名ルールは、「{normalizedFunctionName}LogGroup」になります。


つまり、write-postというLambdaに付随して作成されたロググループは、上記変換ルールに沿ってWriteDashPostLogGroupという論理名で生成されるため、そのリソースをresources.extensionsによって上書き設定します。


このようにして、自動作成されたロググループに、RetentionInDays: '30'という設定を後から追加することができるようになります。


ところが・・・

エラー発生


Error: The CloudFormation template is invalid: Template format error: [/Resources/WriteDashPostLogGroup] Every Resources object must contain a Type member.

あれ・・・?上記リファレンス通りに実行するとエラーになるぞ・・・?



よく上記「論理名変換」の、normalizedFunctionName変換ルールを読み直してみます。


  • 頭文字は大文字に置き換わる
  • -, _は以下の文字に置き換わる
    • - -> Dash
    • _ -> Underscore


あくまで頭文字を大文字にしてくれるだけであって、-,_の後の文字も大文字にしてくれるわけではないのです。

つまり、ケバブケースやスネークケースを完全にキャメルケースに変換してくれるわけではないのです。



実際は

write-postというLambdaに付随して作成されたロググループは、上記変換ルールに沿ってWriteDashPostLogGroupという論理名で生成されるため、そのリソースをresources.extensionsによって上書き設定します。

このように先ほど記載しましたが、write-postというLambdaの論理リソース名はWriteDashpostになり、自動生成されるロググループ名はWriteDashPostLogGroupではなく、WriteDashpostLogGroupが正しいわけですね(postが小文字)。


リファレンスによくある「実行するとエラーになる」例でした。


ちょっとわかりづらくなるので、そもそも論理名はキャメルケースで統一して作成する開発・運用ルールにしておいた方が良さそうです。


最後に

これで自動生成されたリソースに対して、追加で設定を加えることができました。

Serverless Frameworkは結構使っているのですが、リファレンスを調べると実はこんなことができるみたいなことが結構あります。よく調べてから使わないといけませんね。