GitHub+Amazon SNS+Lambda+CloudFormationで簡易CI

先ほどの記事に引き続き。

GitHubからAmazon SNSでLambdaにイベント飛ばせば簡易CIができそうだだったので作ってみた。

処理の概要

  1. GitHubにpush→Amazon SNS→Lambdaにイベント
  2. Lambdaがイベントをフック→cfnスタックを作成
  3. cfnスタックがEC2インスタンスを起動→git clone
  4. bundle exec rspec
  5. cfnスタックは処理完了後に自動的に削除

Lambda Function

こんな感じ。

var Promise = require('bluebird');

var AWS = require("aws-sdk");
AWS.config.update({region: 'ap-northeast-1'});

var cloudformation = Promise.promisifyAll(new AWS.CloudFormation());

var fs = Promise.promisifyAll(require('fs'));

exports.handler = function(event, context) {
  var message = JSON.parse(event.Records[0].Sns.Message);
  var repo_url = message.repository.url;
  var stackName = 'my-stack-' + new Date().getTime();

  fs.readFileAsync('template.json', 'utf8').then(function(data) {
    return cloudformation.createStackAsync({
      StackName: stackName,
      Parameters: [
        {
          ParameterKey: 'Repository',
          ParameterValue: repo_url
        }
      ],
      TemplateBody: data
    });
  }).then(function(data) {
    console.log(data);
  }).then(function() {
    context.succeed('OK');
  }).catch(function(err) {
    context.fail(err);
  });
};

roleにはcfnスタックを作成できる適切な権限を付与しておく。

cfn template

こんな感じ。

{
  "Parameters": {
    "Repository": {
      "Type": "String"
    }
  },
  "Resources": {
    "MyInstance": {
      "Type": "AWS::EC2::Instance",
      "Properties": {
        "ImageId": "ami-cbf90ecb",
        "InstanceType": "t2.micro",
        "SubnetId": "subnet-XXXXXXXX",
        "IamInstanceProfile": "my_role",
        "Tags": [
          {
            "Key": "Name",
            "Value": {"Ref": "AWS::StackId"}
          }
        ],
        "UserData": {
          "Fn::Base64": {
            "Fn::Join": [
              "",
              [
                "#!/bin/bash\n",
                "export PATH=/usr/local/bin:$PATH\n",
                "yum install -y git\n",
                "yum install -y rubygem-io-console\n",
                "gem install bundler\n",
                "git clone ", {"Ref": "Repository"}, " myrepo\n",
                "cd myrepo\n",
                "bundle install\n",
                "bundle exec rspec\n",
                "aws cloudformation delete-stack --stack-name ", {"Ref": "AWS::StackName"}, " --region ", {"Ref": "AWS::Region"}, "\n"
              ]
            ]
          }
        }
      }
    }
  }
}

やってることは、見たとおり。

/usr/local/binにパスを通す必要があった。

Amazon SNS

Lambdaにイベントを飛ばすトピックを作成。

f:id:winebarrel:20150808173745p:plain

GitHubリポジトリ

https://github.com/winebarrel/gh-lambda

f:id:winebarrel:20150808173331p:plain

SNSとの連携を設定しておく。

f:id:winebarrel:20150808174401p:plain

使ってみる

上記のリポジトリで適当にコミットすると

$ git commit --allow-empty -m 'XXX'; git push
[master 1d03048] XXX
Counting objects: 1, done.
Writing objects: 100% (1/1), 189 bytes | 0 bytes/s, done.
Total 1 (delta 0), reused 0 (delta 0)
To git@github.com:winebarrel/gh-lambda.git
   765ff58..1d03048  master -> master

Lambdaにイベントが飛んで

START RequestId: d7f6bdab-3da8-11e5-82cf-81454d6df691
END RequestId: d7f6bdab-3da8-11e5-82cf-81454d6df691
2015-08-08T08:38:30.060Z    d7f6bdab-3da8-11e5-82cf-81454d6df691    { ResponseMetadata: { RequestId: 'd8206580-3da8-11e5-84e4-e722a24172c9' },
  StackId: 'arn:aws:cloudformation:ap-northeast-1:123456789012:stack/my-stack-1439023109307/d8343b90-3da8-11e5-8e97-5001aba754a8' }
REPORT RequestId: d7f6bdab-3da8-11e5-82cf-81454d6df691  Duration: 768.96 ms Billed Duration: 800 ms     Memory Size: 128 MB Max Memory Used: 16 MB

インスタンスが起動して

f:id:winebarrel:20150808174022p:plain

rspecが実行されて

f:id:winebarrel:20150808174134p:plain

インスタンスは削除される。

f:id:winebarrel:20150808174200p:plain