ServerlessFrameworkでLmabda関数名に柔軟性を持たせようとする時の注意点

Lambda関数名に柔軟性をもたせようとしたら、ロググループ名が変わってしまったり、ログが出力されなくなったりしたのでその時の対応についての注意点になります。

##### 普通にserverless.ymlでLmabdaを定義するとこんな感じになると思います。

```serverless.yml
service: demo

provider:
  name: aws
  runtime: python3.7
  stage: ${opt:stage, 'dev'}

functions:
  hello:
    handler: handler.hello
```

この場合、リソースのロググループ名はServerlessFrameworkが以下のようにしてくれます。
論理ID`HelloLogGroup`
物理ID`/aws/lambda/demo-dev-hello`

物理IDはこんな感じですね。
`/aws/lambda/${self:service}-${self:provider.stage}-${Lambda関数名}`

##### ログループ名を表示したり使い回すときはこんな感じで。

```serverless.yml
resources:
  Outputs:
    HelloLogGroup:
      Value: ！Ref HelloLogGroup
```
ただ、これだと関数名を変更するときはロググループの箇所も変更する必要があります。
また、関数が増えると関数名に合わせてロググループの名前を追加するという面倒な作業が増えます。

### 関数名を変えたり、増やしたりするときに面倒くさい！！
と考えて、こんな風に置き換えました。

```env.yml
service: demo
func1: hello 
func2: goodby
```

```serverless.yml
service: ${self:custom.env.service}

provider:
  name: aws
  runtime: python3.7
  stage: ${opt:stage, 'dev'}

custom:
  env: ${file(../env.yml)}

functions:
  func1:
    name: ${self:custom.env.func1}
    handler: handler.${self:custom.env.func1}

  func2:
    name: ${self:custom.env.func2}
    handler: handler2.${self:custom.env.func2}

resources:
  Outputs:
    Func1LogGroup:
      Value: ！Ref Func1LogGroup
    Func2LogGroup:
      Value: ！Ref Func2LogGroup
```

これで変更に柔軟な構成になりましたが、`sls deploy -v` したところ・・
### ログの名前がおかしい？！

```linux
Stack Outputs
Func1LogGroup: /aws/lambda/hello
Func2LogGroup: /aws/lambda/goodby
```
### さらにLambdaを実行してもCloudWatch Logsにログが出力されない！！
functonの名前を`name`で設定したことによって、今までうまくやってくれていたServerlessFrameworkのレールから外れてしまったんでしょうか。。

### ログの名前を戻す＆CloudWatchのIAMロールを付与する

CloudFormationのリソースは`extensions`でOverrideできるため、以下のように自分で組みたてて更新します。さらにCloudWatchのIAMのロールを明示的に追加します。

```common.yml
  Func1LogGroup: !Join
                  - ''
                  - - "/aws/lambda/"
                    - ${self:service}
                    - '-'
                    - ${self:provider.stage}
                    - '-'
                    - ${self:custom.env.func1}
  Func2LogGroup: !Join
                  - ''
                  - - "/aws/lambda/"
                    - ${self:service}
                    - '-'
                    - ${self:provider.stage}
                    - '-'
                    - ${self:custom.env.func2}
``` 

```serverless.yml
service: ${self:custom.env.service}

provider:
  name: aws
  runtime: python3.7
  stage: ${opt:stage, 'dev'}
#### ↓CloudWatch LogsのIAMロール付与 ####
  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - logs:CreateLogGroup
        - logs:CreateLogStream
        - logs:PutLogEvents
        - s3:ListBucket
      Resource: "*"
#### ↑追加 ####

custom:
#### ↓追加 ####
  common: ${file(../../template/common.yml)}
#### ↑追加 ####
  env: ${file(../env.yml)}

functions:
  func1:
    name: ${self:custom.env.func1}
    handler: handler.${self:custom.env.func1}

  func2:
    name: ${self:custom.env.func2}
    handler: handler2.${self:custom.env.func2}

resources:
  Outputs:
    Func1LogGroup:
      Value: ！Ref Func1LogGroup
    Func2LogGroup:
      Value: ！Ref Func2LogGroup
#### ↓リソースのOverride ####
  extensions:
    Func1LogGroup:
      Properties:
        LogGroupName: ${self:custom.common.Func1LogGroup}
    Func2LogGroup:
      Properties:
        LogGroupName: ${self:custom.common.Func2LogGroup}
#### ↑追加 ####
```

これでロググループ名は戻り、Lambda実行時にCloudWatchにログが出力されるようになりました。
めでたし、めでたし。。

```linux
Stack Outputs
Func1LogGroup: /aws/lambda/demo-dev-hello
Func2LogGroup: /aws/lambda/demo-dev-goodby
```

### ・・いや、さらに柔軟にしたい・・！
この状態だと関数の増減のたびに追加、削除する必要があるため、以下のように修正しました。
とりあえず最大4つということにして設定しておきます。
関数の増減があるときに`env.yml`を修正し、まだ作成していない分は`dummy`といれておきます。

```env.yml
service: demo
func1: hello 
func2: goodby
func3: dummy
func4: dummy
```

Conditionsを追加して関数の存在チェックを作成。dummy以外の場合は存在となみす。

```serverless.yml
resources:
  Conditions:
    Func1Exists:
      !Not
        - !Equals
          - ${self:custom.env.func1}
          - dummy
    Func2Exists:
      !Not
        - !Equals
          - ${self:custom.env.func2}
          - dummy
    Func3Exists:
      !Not
        - !Equals
          - ${self:custom.env.func3}
          - dummy
    Func4Exists:
      !Not
        - !Equals
          - ${self:custom.env.func3}
          - dummy
```

`resources`はこんな感じにします。
`!Ref Func3LogGroup`みたいな書き方をしてしまうと、そのリソースが存在しないときは、たとえ`Conditions`を使っていてもエラーになります。なので`self:custom`から取るようにしています。

```serverless.yml
〜〜〜〜〜省略〜〜〜〜
resources:
  Conditions:
    Func1Exists:
      !Not
        - !Equals
          - ${self:custom.env.func1}
          - dummy
    Func2Exists:
      !Not
        - !Equals
          - ${self:custom.env.func2}
          - dummy
    Func3Exists:
      !Not
        - !Equals
          - ${self:custom.env.func3}
          - dummy
    Func4Exists:
      !Not
        - !Equals
          - ${self:custom.env.func4}
          - dummy

  extensions:
    Func1LogGroup:
      Condition: Func1Exists
      Properties:
        LogGroupName: ${self:custom.common.Func1LogGroup}
    Func2LogGroup:
      Condition: Func2Exists
      Properties:
        LogGroupName: ${self:custom.common.Func2LogGroup}
    Func3LogGroup:
      Condition: Func3Exists
      Properties:
        LogGroupName: ${self:custom.common.Func3LogGroup}
    Func4LogGroup:
      Condition: Func4Exists
      Properties:
        LogGroupName: ${self:custom.common.Func4LogGroup}

  Outputs:
    Func1LogGroup:
      Condition: Func1Exists
      Value: ${self:custom.common.Func1LogGroup}
    Func2LogGroup:
      Condition: Func2Exists
      Value: ${self:custom.common.Func2LogGroup}
    Func3LogGroup:
      Condition: Func3Exists
      Value: ${self:custom.common.Func3LogGroup}
    Func4LogGroup:
      Condition: Func4Exists
      Value: ${self:custom.common.Func4LogGroup}
```

これで`sls deploy -v`すると・・

```
Error: Func1LogGroup: Sorry, extending the Condition resource attribute at this point is not supported. Feel free to propose support for it in the Framework issue tracker: https://github.com/serverless/serverless/issues

```

### なんかエラーでてる！！
翻訳すると・・
> 申し訳ありませんが、現時点では Condition リソース属性の拡張はサポートされていません。Framework issue tracker: https://github.com/serverless/serverless/issues でサポートを提案してください。

ということらいいです。
extensionsにConditionは使えないらしいです。OutputsにConditionは使えたのでとりあえずは良かったということで・・。
extensionsは、Conditionが使える実装がされるまでは、関数の増減によってコメントをオン・オフして対応します。

#####最終的にはこんな感じになりました。
env.ymlに設定した関数の分だけロググループが表示されます。

```linux
Stack Outputs
Func1LogGroup: /aws/lambda/demo-dev-hello
Func2LogGroup: /aws/lambda/demo-dev-goodby
```

```serverless.yml
service: ${self:custom.env.service}

provider:
  name: aws
  runtime: python3.7
  stage: ${opt:stage, 'dev'}
  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - logs:CreateLogGroup
        - logs:CreateLogStream
        - logs:PutLogEvents
        - s3:ListBucket
      Resource: "*"

custom:
  common: ${file(../../template/common.yml)}
  env: ${file(../env.yml)}

functions:
  func1:
    name: ${self:custom.env.func1}
    handler: handler.${self:custom.env.func1}

  func2:
    name: ${self:custom.env.func2}
    handler: handler2.${self:custom.env.func2}

resources:
  Conditions:
    Func1Exists:
      !Not
        - !Equals
          - ${self:custom.env.func1}
          - dummy
    Func2Exists:
      !Not
        - !Equals
          - ${self:custom.env.func2}
          - dummy
    Func3Exists:
      !Not
        - !Equals
          - ${self:custom.env.func3}
          - dummy
    Func4Exists:
      !Not
        - !Equals
          - ${self:custom.env.func4}
          - dummy

  extensions:
    Func1LogGroup:
#      実装されたらコメント外す
#      Condition: Func1Exists
      Properties:
        LogGroupName: ${self:custom.common.Func1LogGroup}
    Func2LogGroup:
#      実装されたらコメント外す
#      Condition: Func2Exists
      Properties:
        LogGroupName: ${self:custom.common.Func2LogGroup}

# extensionsは、Conditionが実装されるまで、関数の増減の都度コメントのオンオフを行う
#    Func3LogGroup:
#      実装されたらコメント外す
#      Condition: Func3Exists
#      Properties:
#        LogGroupName: ${self:custom.common.Func3LogGroup}
#    Func4LogGroup:
#      実装されたらコメント外す
#      Condition: Func4Exists
#      Properties:
#        LogGroupName: ${self:custom.common.Func4LogGroup}

  Outputs:
    Func1LogGroup:
      Condition: Func1Exists
      Value: ${self:custom.common.Func1LogGroup}
    Func2LogGroup:
      Condition: Func2Exists
      Value: ${self:custom.common.Func2LogGroup}
    Func3LogGroup:
      Condition: Func3Exists
      Value: ${self:custom.common.Func3LogGroup}
    Func4LogGroup:
      Condition: Func4Exists
      Value: ${self:custom.common.Func4LogGroup}
```

```env.yml
service: demo
func1: hello 
func2: goodby
func3: dummy
func4: dummy
```

```common.yml
  Func1LogGroup: !Join
                  - ''
                  - - "/aws/lambda/"
                    - ${self:service}
                    - '-'
                    - ${self:provider.stage}
                    - '-'
                    - ${self:custom.env.func1}
  Func2LogGroup: !Join
                  - ''
                  - - "/aws/lambda/"
                    - ${self:service}
                    - '-'
                    - ${self:provider.stage}
                    - '-'
                    - ${self:custom.env.func2}
  Func3LogGroup: !Join
                  - ''
                  - - "/aws/lambda/"
                    - ${self:service}
                    - '-'
                    - ${self:provider.stage}
                    - '-'
                    - ${self:custom.env.func3}
  Func4LogGroup: !Join
                  - ''
                  - - "/aws/lambda/"
                    - ${self:service}
                    - '-'
                    - ${self:provider.stage}
                    - '-'
                    - ${self:custom.env.func4}

``` 

めでたし、めでたし・・と思っていたら

## 新しい問題発覚！！
上記のやり方はロググループに言及していましたが、Lambdaの関数名にも同じことが言えます。

本来は{service}-{stage}-{function}という形式で作られるはずが、{function}で作られてしまいます。
（上記の例のとおりだと、demo-dev-helloではなく、helloで作られる）
これも同様に`extensions`で無理やり変えてしまえば問題ないように思えました。

### 気づいたきっかけ
ある時、Lambdaの起動をS3をトリガーにするために、以下のように設定を加えるとうまく機能しませんでした。

```serverless.yml
functions:
  func1:
    name: ${self:custom.env.func1}
    handler: handler.${self:custom.env.func1}
    events:
      - s3:
          bucket: ${self:custom.common.Func2Bucket1}
          event: s3:ObjectCreated:*
          existing: true

```
Lambda関数の一覧には(`extensions`で無理やり変えたため)`demo-dev-hello`で作られていますが、S3のトリガを確認すると`hello`で設定してます。
この場合、Lambda関数名に`hello`は存在していないため、トリガが機能しません。

想像ですが、S3のトリガ設定のほうが`extensions`でLambda関数名を書き換えるより順序が早いんですかね・・。

## 解決方法
結局の以下のような形にすることで問題はなくなりました。

* `extensions`の使用はやめる
* `name`に{service}-{stage}-{function}の形式に沿う形で設定する


```serverless.yml
functions:
  func1:
    name: ${self:custom.env.service}-${self:provider.stage}-${self:custom.env.func1}
    handler: handler.${self:custom.env.func1}
    events:
      - s3:
          bucket: ${self:custom.common.Func2Bucket1}
          event: s3:ObjectCreated:*
          existing: true

```

最初からこうしておけという話ですね。。