【AWS】CodePipelineから呼び出したLambdaがずっと進行中になっている問題と対応

## 問題

**ずっとインジケータがぐるぐるしている**

<img alt="スクリーンショット 2019-11-08 23.47.25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312085/fb0932ea-9971-0448-dbff-ff7a057e5188.png">
<font color="Silver">(10分立ってもそのままで)もう気が狂う！！！</font>


S3へのデプロイ完了後にCloudFrontのキャッシュを消すLambdaを呼びたいなーと思ったときに躓いたのでまとめ。

## 原因
**`codepipeline:PutJobSuccessResult(※)`を返却しないため**
※ 失敗の場合は`codepipeline:PutJobFailedResult`

### PutJobSuccessResultって？
> PutJobSuccessResult
> ジョブワーカーによってパイプラインに返されたジョブの成功をレポートするために必要です。カスタムアクションにのみ使用されます。
> https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/permissions-reference.html より引用

![images.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312085/31be3263-863c-ffca-5bf0-40d66bc4f4af.png)


### 要約すると

CodePipelineは自身が呼び出した**カスタムアクション(Lambdaとか)の結果を`PutJobSuccessResult`、`PutJobFailedResult`という決まった形式**でしか受け取らない。
**それ以外の形式で返しても無視するので、ずっとLambdaから結果こないなーまだかなーとインジケータがぐるぐる**してる。

### PutJobSuccessResultを返した時
<img alt="スクリーンショット 2019-11-08 23.56.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312085/acb53dd9-3cf1-b1a9-5242-b581638e7b34.png">

### PutJobFailedResultを返した時
<img alt="スクリーンショット 2019-11-09 0.09.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312085/b39192f5-037b-7dd1-f8aa-a7f3aba6456d.png">

<img alt="スクリーンショット 2019-11-09 0.09.58.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312085/1ed59e0a-10bc-2e42-efda-9ebdb8a3ed47.png">

<font color="Silver">やったぜ。</font>

## 対応
**Lambda上で`PutJobSuccessResult`、`PutJobFailedResult`を返すコードを書くことでOK。**

実際にLambdaで`PutJobSuccessResult`、`PutJobFailedResult`を返すための方法を言語別に。

### Python
```python:Lambdaランタイム_Python3.7
import json
import boto3

def lambda_handler(event, context):
    codepipeline = boto3.client('codepipeline')
    
    # CodePipelineに結果(成功)を返す
    codepipeline.put_job_success_result(jobId = event['CodePipeline.job']['id'])
    
    # CodePipelineに結果(失敗)を返す
    # codepipeline.put_job_failure_result(
    #     jobId = event['CodePipeline.job']['id'],
    #     failureDetails={
    #         'type': 'JobFailed',
    #         'message': 'Failed test.'
    #     }
    # )
    
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }
```

### Ruby
```ruby:Lambdaランタイム_Ruby2.5
require 'json'
require 'aws-sdk'

def lambda_handler(event:, context:)
    codepipeline = Aws::CodePipeline::Client.new()
    
    # CodePipelineに結果(成功)を返す
    codepipeline.put_job_success_result({job_id: event["CodePipeline.job"]['id']})
    
    # CodePipelineに結果(失敗)を返す
    # codepipeline.put_job_failure_result({
    #    job_id: event["CodePipeline.job"]['id'], 
    #    failure_details: {
    #        type: "JobFailed", # accepts {JobFailed, ConfigurationError, PermissionError, RevisionOutOfSync, RevisionUnavailable, SystemUnavailable}
    #        message: "Failed test."
    #    }
    # })
    
    { statusCode: 200, body: JSON.generate('Hello from Lambda!') }
end
```

### Node.js
```javascript:Lambdaランタイム_Node.js10.x
var AWS = require('aws-sdk');

exports.handler = function(event, context) {
    var codepipeline = new AWS.CodePipeline();
    
    // CodePipelineに結果(成功)を返す
    codepipeline.putJobSuccessResult({jobId: event["CodePipeline.job"].id}, function(err, data) {
        if(err) {
            context.fail(err);
        } else {
            context.succeed('Success test.');
        }
    });
    
    // CodePipelineに結果(失敗)を返す
    // var params = {
    //     jobId: event["CodePipeline.job"].id,
    //     failureDetails: {
    //         message: JSON.stringify('Failed test.'),
    //         type: 'JobFailed',
    //     }
    // };
    // codepipeline.putJobFailureResult(params, function(err, data) {
    //     context.fail('Failed test.');
    // });
    
    const response = {
        statusCode: 200,
        body: JSON.stringify('Hello from Lambda!'),
    };
    return response;
};
```
<font color="Silver">普段Node.js書かないんでこれが必要最低限のコードかわっかんねー</font>

## コードコピペしたのにまだぐるぐるしてるじゃねーか！

まずLambdaのCloudWatchログを見てみよう。

### AccessDeniedException

Lambdaの実行結果としてCloudWatchLogにこんなんがでる。

```json
{
   "errorType": "AccessDeniedException",
   "errorMessage": "User: arn:aws:sts::1111111111111:assumed-role/codetest-role-xxxxxxxxx/codetest is not authorized to perform: codepipeline:PutJobSuccessResult",
    ...etc
}
```

LambdaからCodePipelineに結果を返すのにも権限がいるのだ...。

Lambdaに割り当てられてるIAMロールに以下のIAMポリシーを追加しておく必要あり。

```json:IAMポリシー
{
  "Version": "2012-10-17", 
  "Statement": [
    {
      "Action": [ 
        "logs:*"
      ],
      "Effect": "Allow", 
      "Resource": "arn:aws:logs:*:*:*"
    },
    {
      "Action": [
        "codepipeline:PutJobSuccessResult",
        "codepipeline:PutJobFailureResult"
        ],
        "Effect": "Allow",
        "Resource": "*"
     }
  ]
} 
```

誤りなどあったら教えてくだせー。