CloudFormationをCircleCIでCIする


cloudpack大阪の佐々木です。

またもCircleCIです。
今度はCloudFormationをテストをCircleCIでやってみます。
テストはawspecを使います。

[awspec](https://github.com/k1LoW/awspec)

# 設定

ソースコードはこちらに。
https://github.com/taishin/cfn-ci

### CloudFormation

[template.cform](https://github.com/taishin/cfn-ci/blob/master/template.cform)
VPC上にEC2を1台たてているだけです。
CloudFormationで作成されたリソース(セキュリティグループ等)は、名前にランダムな文字列が付与されていたり、テストコードに代入しにくいので、Tagで名前を設定しています。

### awspecテストコード

- EC2
 
EC2はImageID、InstanceType、パブリックIPアドレスをもっていること、
適用されているセキュリティグループ、VPC等の確認をしています。
 ####で囲まれている部分はCircleCIの変数になるので、circle.ymlで書き換えるようにしています。(後述)

```ruby:ec2_spec.rb-template
require 'spec_helper'
describe ec2('####INSTANCEID####') do
  it { should exist }
  it { should be_running }
  its(:image_id) { should eq '####IMAGEID####' }
  its(:instance_type) { should eq '####INSTANCETYPE####' }
  its(:public_ip_address) { should match "^\\d{1,3}\.\\d{1,3}\.\\d{1,3}\.\\d{1,3}$" }
  its(:key_name) { should eq '####KEYNAME####' }
  it { should have_security_group('CFn-CI-SG-HTTP') }
  it { should have_security_group('CFn-CI-SG-Base') }
  it { should belong_to_vpc('CFn-CI-VPC') }
  it { should belong_to_subnet('CFn-CI-Subnet1A') }
end
```

- VPC

VPCはCIDRのIPアドレス、ルーティングテーブルの確認をしています。

```ruby:vpc_spec.rb
require 'spec_helper'

describe vpc('CFn-CI-VPC') do
  it { should exist }
  it { should be_available }
  its(:cidr_block) { should eq '10.0.0.0/16' }
  it { should have_route_table('CFn-CI-PubRT') }
end

describe subnet('CFn-CI-Subnet1A') do
  it { should exist }
  it { should be_available }
  it { should belong_to_vpc('CFn-CI-VPC') }
  its(:cidr_block) { should eq '10.0.1.0/24' }
end

describe subnet('CFn-CI-Subnet1C') do
  it { should exist }
  it { should be_available }
  it { should belong_to_vpc('CFn-CI-VPC') }
  its(:cidr_block) { should eq '10.0.2.0/24' }
end

describe route_table('CFn-CI-PubRT') do
  it { should exist }
  it { should belong_to_vpc('CFn-CI-VPC') }
  it { should have_route('10.0.0.0/16').target(gateway: 'local') }
  it { should have_route('0.0.0.0/0').target(gateway: 'CFn-CI-IGW') }
  it { should have_subnet('CFn-CI-Subnet1C') }
  it { should have_subnet('CFn-CI-Subnet1A') }
end
```

- SecurityGroup

セキュリティグループはルールの確認です。

```ruby:sg_spec.rb
require 'spec_helper'

describe security_group('CFn-CI-SG-Base') do
  it { should exist }
  its(:inbound) { should be_opened(22).protocol('tcp').for('0.0.0.0/0') }
  its(:outbound) { should be_opened }
  its(:inbound_rule_count) { should eq 1 }
  its(:outbound_rule_count) { should eq 1 }
  its(:inbound_permissions_count) { should eq 1 }
  its(:outbound_permissions_count) { should eq 1 }
  it { should belong_to_vpc('CFn-CI-VPC') }
end

describe security_group('CFn-CI-SG-HTTP') do
  it { should exist }
  its(:inbound) { should be_opened(80).protocol('tcp').for('0.0.0.0/0') }
  its(:outbound) { should be_opened }
  its(:inbound_rule_count) { should eq 1 }
  its(:outbound_rule_count) { should eq 1 }
  its(:inbound_permissions_count) { should eq 1 }
  its(:outbound_permissions_count) { should eq 1 }
  it { should belong_to_vpc('CFn-CI-VPC') }
end
```

### circle.yml

circle.ymlではCloudFormationを実行し、EC2のInstanceIDを取得します。
SSH Key、インスタンスタイプ、AMI IDは変数で変更できるようにしているので、CloudFormation起動後、ec2_spec.rb-templateをsedで変更し、ec2_spec.rbを生成しています。

```yaml:circle.yml
machine:
  timezone: Asia/Tokyo
  ruby:
    version:
      2.1.2
  environment:
    AWS_AMI_ID: ami-6154bb00
    AWS_INSTANCE_TYPE: t2.micro
    AWS_CF_STACK_NAME: cfn-ci

dependencies:
  pre:

test:
  pre:
    - aws cloudformation create-stack --stack-name ${AWS_CF_STACK_NAME} --template-body file://template.cform --parameters ParameterKey=KeyName,ParameterValue=$AWS_KEY_NAME ParameterKey=ImageId,ParameterValue=$AWS_AMI_ID ParameterKey=InstanceType,ParameterValue=$AWS_INSTANCE_TYPE
    - aws cloudformation wait stack-create-complete --stack-name ${AWS_CF_STACK_NAME}
    - aws cloudformation describe-stacks --stack-name ${AWS_CF_STACK_NAME} | jq -r '.Stacks[].Outputs[] | select(.OutputKey == "InstanceId") | .OutputValue' > /tmp/instanceid.txt
    - sed -e "s/####INSTANCEID####/`cat /tmp/instanceid.txt`/g" -e "s/####KEYNAME####/$AWS_KEY_NAME/g" -e "s/####IMAGEID####/$AWS_AMI_ID/g" -e "s/####INSTANCETYPE####/$AWS_INSTANCE_TYPE/g" spec/ec2_spec.rb-template > spec/ec2_spec.rb
    
  override:
    - bundle exec rake spec
  post:
    - aws cloudformation delete-stack --stack-name ${AWS_CF_STACK_NAME}
```

### CircleCI

CircleCIの画面上の設定は、
http://qiita.com/taishin/items/89b42106582a2aa5495b
と同じく、

- AWS Permissions
    - AccessKey
	- SecrectAccesskey
- Environment Variables
	- AWS_DEFAULT_REGION
	- AWS_KEY_NAME
	
を設定します。

# 実行

Buildさせるとこんな感じでテストして、終わればCloudFormationを削除してくれます。

![Kobito.WEGY7o.png](https://qiita-image-store.s3.amazonaws.com/0/3246/fc051da6-6dd3-c580-4439-fa700d8f25d3.png "Kobito.WEGY7o.png")