AWSにて、S3のイベントをトリガにLambdaからJenkinsのジョブの実行を導入しました

# 参考URL
- [S3 + Lambdaを使って、バックアップ完了をhipchatに通知する - Qiita](http://qiita.com/takuma_yoshida/items/416e8fbfa01a8b4de8b6)
- [S3にPUTしたらAWS LambdaからYoする ｜ Developers.IO](http://dev.classmethod.jp/cloud/aws/to-yo-from-lambda/)
- [S3にアップロードしたCSVファイルをLambda経由でAmazon RedshiftにCOPYする – AWS Lambda Advent Calendar 2014：2日目 ｜ Developers.IO](http://dev.classmethod.jp/cloud/aws/copy-csv-from-s3-to-redshift-by-lambda/)
- [初めてのJavaScript、初めてのAWS Lambda ｜ Developers.IO](http://dev.classmethod.jp/cloud/aws/1st-javascript-1st-lambda/)
- [node.jsのいろいろなモジュール17 – asyncで非同期処理のフロー制御 ｜ Developers.IO](http://dev.classmethod.jp/server-side/asyn/)
- [The Open Universe: Basic Authentication with Node.js Request Module](http://www.ossmentor.com/2015/05/basic-authentication-with-nodejs.html)
- [（祝）AWS LambdaのVPC利用が可能になりました ｜ Developers.IO](http://dev.classmethod.jp/cloud/aws/aws-lambda-vpc/)
- [AWS Lambda編～S3イベント通知機能を実装する～｜Event Notification ストレージ監視 CloudWatch logs｜ | ナレコムAWSレシピ](http://recipe.kc-cloud.jp/archives/7930)
- [LambdaからセキュアにRDSに接続する | ナレコムAWSレシピ](http://recipe.kc-cloud.jp/archives/7388)
- [\[node\]AWS LambdaをJavascriptから呼び出す ｜ Developers.IO](http://dev.classmethod.jp/cloud/aws/node-lambda/)

# これをやった経緯
- 他システムとのデータ連携をCSVファイルを介して、データ連携する要件が発生
- そのデータをS3のような「耐久性」、「可用性」、「安全性」の高いストレージを用意して受け渡す必要があった
- 担当システムではバッチ処理をJenkinsで管理しており、今回もそれを利用したかった
- 定期実行でも対応はできたが、それだとかっこ悪いなーと思ったので、S3にファイル配置されたときだけ自動実行したかった

# 手段検討
参考URL１つ目のS3のイベントをLambdaで拾って、hipchatに通知できるなら、Jenkinsのジョブもたたけるじゃないか！
ということで、下記のような仕組みを妄想ｗ

1. 他システムより、CSVファイルがS3にアップロードされる
2. S3のイベントにて、オブジェクトがCreateされた際に、イベント発行
3. 発行されたイベントをLambdaで受取り、Jenkinsのジョブをキック。ついでにHipchatにも通知する。

# 実現方法
1. まずは、該当のCSVファイルを取込むバッチ処理を作ることだが、それは割愛します。
2. 処理の流れの初めであるS3へのイベントの設定から行いたいが、手順は逆で、Lambdaのfunctionから作成します。
![Lambda Management Console.jpg](https://qiita-image-store.s3.amazonaws.com/0/14268/1556750e-6a47-4a95-4a33-f5526942c0be.jpeg)

3. 今回は言語は「Node.js」、コードはインラインで作成せず、ローカルで作成して、ZIPファイルでアップしました。
   また、Lambda用のロールの作成がちょっとてこずりました。そこについては後述しますが、今は一旦、「lambda_s3_exec_role」で作成してください。
![Lambda Management Console (3).jpg](https://qiita-image-store.s3.amazonaws.com/0/14268/5f8805f7-6a2b-5d58-f05d-c345e7f3e56b.jpeg)

4. Create Functionを選択し、Lambda関数を作成してください。
![Lambda Management Console (2).jpg](https://qiita-image-store.s3.amazonaws.com/0/14268/50037d2c-f27c-9d57-9f61-dc8744c79097.jpeg)

5. ここまできたら、後はLambdaで動かすコードを作成しましょう！下記のコードについては、、NodeJSが初めてのため、ソースが初心者コードになってます。。ご指摘、改善内容提案などいただけると嬉しいです！

```node.js
console.log('Loading event');
var prodaws = require('aws-sdk');
var s3 = new prodaws.S3({apiVersion: '2006-03-01'});
var request = require('request');
var async = require('async');
var auth = "Basic " + new Buffer("XXXXXX:XXXXXX").toString("base64");

exports.handler = function(event, context) {
    // *************************************************************************
    // * Lambdaをno VPC以外で動かす場合には必要になりそう
    // * VPC指定だと、privateサブネット経由だと、NATインスタンスのGIPになるっぽい
    // *************************************************************************
/*
    var exec = require('child_process').exec;
    var cmd = "curl inet-ip.info";
    var child = exec(cmd, function(error, stdout, stderr) {
        if (!error) {
            // IP => CIDR
            var ipAddress = stdout.replace(/[\n\r]/g,"") + "/32";
            console.log('Lambda Global Ip: ' + ipAddress);
            var params = {
              GroupId: 'sg-XXXXXX',
              IpPermissions: [
                {
                  FromPort: 80,
                  ToPort: 80,
                  IpProtocol: 'tcp',
                  IpRanges: [
                    {
                      CidrIp: ipAddress
                    },
                  ]
                },
              ],
            };
            ec2.authorizeSecurityGroupIngress(params, function(err, data) {
                if (err){
                    console.log(err);
                } else {
                    console.log('Success Add Lambda Global Ip to jenkins security group! ');
                }
            });
        } else {
            console.log("error code: " + error.code + ", err: " + error);
            context.done(error,'lambda');
        }
    });
*/
    console.log('Received event:');
    console.log(JSON.stringify(event, null, '  '));
    // Get the object from the event and show its content type
    var bucket = event.Records[0].s3.bucket.name;
    console.log(bucket);
    var key = event.Records[0].s3.object.key;
    console.log(key);
    console.log(event.Records[0].s3.object);
    console.log('Sending hipchat:');
    async.waterfall([
        function download(callback){
            s3.getObject({Bucket:bucket, Key:key},
               function(err,data) {
                 if (err) {
                     console.log('error getting object ' + key + ' from bucket ' + bucket +
                        '. Make sure they exist and your bucket is in the same region as this function.');
                     context.done('error','error getting file'+err);
                 }
                 else {
                     console.log('CONTENT TYPE:',data.ContentType);
                     callback(null, 1);
                 }
               }
            );
        },
        function postjenkins(arg1, callback){
            console.log('Start kick jenkins jobs:');
            request.post({
                    url : 'http://jenkins.example.com/job/test-echo-job/build',
                    headers: { 'Authorization': auth },  // Basic認証が必要な場合は入れます。
                    form: { }
                },
                function (error, response, body) {
                    if (!error && response.statusCode == 200) {
                        console.log(body);
                    }
                    if (error) {
                        console.log(error);
                    }
                }
            );
            console.log('End kick jenkins jobs:');
            callback(null, 2);
        },        
        function posthipchat(arg1, callback){
            console.log('Start Sending hipchat:');
            request.post(
                'https://api.hipchat.com/v1/rooms/message?format=json&auth_token=XXXXXXXXXXX',
                { form: { 'room_id': 'XXXXXXX' , 'from': 'Lambda Script' , 'message': 'おら Lambda.よろしくね。 バッチを起動します' , 'message_format': 'text' } },
                function (error, response, body) {
                    if (!error && response.statusCode == 200) {
                        console.log(body);
                    }
                }
            );
            console.log('End Sending hipchat:');
            callback(null, 'done');
        }
    ],
        function(err){
            if(err){ console.log('Error:' + err); }
            console.log('all done....');
            context.done(null,'process all done....');
        }
    );
};
```

6. ひたすらLambdaの画面にて、アップロード、テスト、アップロード、テスト・・・・・
7. 動いたら、こんな感じでHipchatで反応があります！

![caad - HipChat (1).jpg](https://qiita-image-store.s3.amazonaws.com/0/14268/a10e61eb-7ce4-a18d-0504-388009fb2173.jpeg)

8. ここまで動かせたら、後は、S3のイベントでフックして実行されるように設定をするだけです。送信先を作成したLambda関数にして、イベントは「ObjectCreated」でいけます！

![S3 Management Console.jpg](https://qiita-image-store.s3.amazonaws.com/0/14268/238c2dc9-3472-0893-ee24-368933570096.jpeg)


# 注意点
- 今回は、LambdaがAWSのリソースへのアクセスとして、S3とVPCへアクセスをするため、双方がアクセス可能なロールを作成しています。それが、下記。このロールが必要なところに少しハマりました。。。
![IAM Management Console (1).jpg](https://qiita-image-store.s3.amazonaws.com/0/14268/f7f9455f-4477-54e5-3437-533ff6e1b305.jpeg)


- また、上記のコード上でもコメントで記載してますが、今回はVPC内のNATインスタンス経由でJekinsやHipchatへアクセスすることで、Jenkinsのセキュリティグループへのアクセス許可を固定化できましたが、「No VPC」で利用する場合は、Lambdaが実行された際に、GlobalIPがわからないため、GlobalIPを取得し、さらにJenkinsのセキュリティグループでそれを許可させるという処理が必要になります！これもかなり悩みましたが、VPCへのアクセスにすることで、NATのGlobalIPになることがわかり、なんとか解決しました。