gvizでAWSの構成図を動的に作成する


## 仕様
* VPC + Subnet + EC2 + ELBを表示
* インスタンスは指定した属性・タグを表示
* ELBは参加しているEC2のInService/OutOfServiceが分かるように色分け（Inservice=青, OutofService=赤)
* VPCごとに画像を出力

## 出力結果
### AWS構成
* VPCは１つ（"dev"）
* Subnetは２つ（"sub1" , "sub2"）
* EC2は３つ（"dev1", "dev2", "dev3")
 * タグは Name, status, role
* ELBは１つ（"elb1")

![20160229_003815.jpg](https://qiita-image-store.s3.amazonaws.com/0/10862/fa60051a-473e-bed1-62c9-b2ae245ae7e6.jpeg)

### 出力結果
![dev.png](https://qiita-image-store.s3.amazonaws.com/0/10862/122110b9-494d-c6cd-139f-a50c8c16c4f0.png)


## セットアップ
```
sudo yum install -y graphviz
gem install gviz
```

## コード
``` ruby
# -*- coding: utf-8 -*-
require 'json'
require 'gviz'

DISPLAY_INSTANCE_TAG  = ['Name', 'status', 'role']
DISPLAY_INSTANCE_ATTR = ['PrivateIpAddress']

def get_tag_value(tags, key)
  return nil if tags.nil? or tags == []
  tags.each do |k, v|
    next unless k
    return k['Value'] if k['Key'].downcase == key.downcase
  end
  nil
end

def get_vpc_info
  ret = {}
  JSON.parse(`aws ec2 describe-vpcs`)['Vpcs'].each do |vpc|
    ret[vpc['VpcId']] = { 'name' => get_tag_value(vpc['Tags'], "name"), 'vpc_id' => vpc['VpcId'], 'subnets' => [], 'elbs' => [] }
  end
  ret
end

def get_subnet_info
  ret = {}
  JSON.parse(`aws ec2 describe-subnets`)['Subnets'].each do |subnet|
    name = get_tag_value(subnet['Tags'], "name")
    ret[subnet['SubnetId']] = { 'name' => get_tag_value(subnet['Tags'], "name"), 'subnet_id' => subnet['SubnetId'], 'vpc_id' => subnet['VpcId'], 'instances' => [] }
  end
  ret
end

def get_instance_info
  ret = {}
  JSON.parse(`aws ec2 describe-instances`)['Reservations'].each do |i|
    i['Instances'].each do |inst|
      attr = {'instance_id' => inst['InstanceId'], 'subnet_id' => inst['SubnetId']}
      # 表示させたいタグを取得＆設定
      DISPLAY_INSTANCE_TAG.each do |tag|
        attr.merge!(tag => get_tag_value(inst['Tags'], tag))
      end
      # 表示させたい属性を取得＆設定
      DISPLAY_INSTANCE_ATTR.each do |attr_key|
        attr.merge!(attr_key => inst[attr_key])
      end
      ret[inst['InstanceId']] = attr
    end
  end
  ret
end


def get_elb_info
  ret = {}
  JSON.parse(`aws elb describe-load-balancers`)['LoadBalancerDescriptions'].each do |e|
    instance_ids = []
    e['Instances'].select {|inst| instance_ids << inst['InstanceId'] if inst['InstanceId'] }
    ret[e['LoadBalancerName']] = { 'name' => e['LoadBalancerName'], 'vpc_id' => e['VPCId'], 'instances' => instance_ids, 'health' => {} }
  end
  ret
end

def get_elb_health(elb_name)
  ret = {}
  JSON.parse(`aws elb describe-instance-health --load-balancer-name #{elb_name}`)['InstanceStates'].each do |e|
    ret[e['InstanceId']] =  e['State']
  end
  ret
end

# VPC情報を取得
vpc = get_vpc_info
# Subnet情報の取得
subnet = get_subnet_info
# VPC情報にSubnetを追加
subnet.each do |subnet_id, v|
  next unless vpc[v['vpc_id']]
  vpc[v['vpc_id']]['subnets'] << subnet_id
end
# EC2情報を取得
instance = get_instance_info
# Subnet情報にEC2を追加
instance.each do |inst_id, v|
  next unless subnet[v['subnet_id']]
  subnet[v['subnet_id']]['instances'] << inst_id
end
# ELB情報を取得
elb = get_elb_info
# VPC情報にELBを追加
elb.each do |elb_name, v|
  next unless vpc[v['vpc_id']]
  vpc[v['vpc_id']]['elbs'] << elb_name
  v['health'] = get_elb_health(elb_name)
end

vpc.each do |vpc_id, v|
  Graph do
   subgraph do
      global label:"#{vpc[vpc_id]['name']}"

      v['subnets'].each_with_index do |subnet_id, i|
        subgraph("cluster#{i+1}") do
          global label:"#{subnet[subnet_id]['name']}"
          subnet[subnet_id]['instances'].each do |inst_id|

            label = "{ "
            DISPLAY_INSTANCE_TAG.each_with_index do |tag, i|
              label += " | " if i > 0
              label += instance[inst_id][tag] ? instance[inst_id][tag] : ""
            end
            DISPLAY_INSTANCE_ATTR.each_with_index do |at, i|
              label += " | "
              label += instance[inst_id][at] ? instance[inst_id][at] : ""
            end
            label += " }"
            node:"#{instance[inst_id]['Name'].gsub("-","")}",
            :shape => :Mrecord,
            :style => :filled,
            :fillcolor => :yellow,
            :label => label
          end
        end
      end

      v['elbs'].each do |elb_name|
        node:"#{elb_name.gsub("-","")}"

        instance_names = []
        elb[elb_name]['instances'].each do |inst_id|
          instance_names << instance[inst_id]['Name'].gsub("-","")

          color = elb[elb_name]['health'][inst_id] == 'InService' ? "blue" : "red"
          edge :"#{elb_name.gsub("-","")}_#{instance[inst_id]['Name'].gsub("-","")}", color:"#{color}"
        end
        route :"#{elb_name.gsub("-","")}" => instance_names
      end
    end
    save(:"#{v['name']}", :png)
  end
  `dot -Tpng #{v['name']}.dot -o #{v['name']}.png`
end

```