簡易EC2クライアント

ちょっとしたメタ情報を取るのにec2-api-toolsだと遅いし(JAVA_HOMEとかの設定も必要だし)、gemでライブラリをインストールするのもいやなので、rubyの標準ライブラリだけで簡易的なクライアントを作ってみた。*1

#!/usr/bin/env ruby
require 'cgi'
require 'base64'
require 'net/https'
require 'openssl'
require 'optparse'
require 'rexml/document'

class EC2Client
  API_VERSION = '2011-12-01'
  SIGNATURE_VERSION = 2

  def initialize(accessKeyId, secretAccessKey, endpoint = 'ec2.us-east-1.amazonaws.com', algorithm = :SHA256)
    @accessKeyId = accessKeyId
    @secretAccessKey = secretAccessKey
    @endpoint = endpoint
    @algorithm = algorithm
  end

  def query(action, params = {})
    params = {
      :Action           => action,
      :Version          => API_VERSION,
      :Timestamp        => Time.now.getutc.strftime('%Y-%m-%dT%H:%M:%SZ'),
      :SignatureVersion => SIGNATURE_VERSION,
      :SignatureMethod  => "Hmac#{@algorithm}",
      :AWSAccessKeyId   => @accessKeyId,
    }.merge(params)

    signature = aws_sign(params)
    params[:Signature] = signature

    Net::HTTP.version_1_2
    https = Net::HTTP.new(@endpoint, 443)
    https.use_ssl = true
    https.verify_mode = OpenSSL::SSL::VERIFY_NONE

    https.start do |w|
      req = Net::HTTP::Post.new('/',
        'Host' => @endpoint,
        'Content-Type' => 'application/x-www-form-urlencoded'
      )

      req.set_form_data(params)
      res = w.request(req)

      res.body
    end
  end

  private
  def aws_sign(params)
    params = params.sort_by {|a, b| a.to_s }.map {|k, v| "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}" }.join('&')
    string_to_sign = "POST\n#{@endpoint}\n/\n#{params}"
    digest = OpenSSL::HMAC.digest(OpenSSL::Digest.const_get(@algorithm).new, @secretAccessKey, string_to_sign)
    Base64.encode64(digest).gsub("\n", '')
  end
end

access_key_id = nil
secret_access_key = nil
endpoint = 'ec2.us-east-1.amazonaws.com'

ARGV.options do |opt|
  opt.on('-k', '--access-key-id ACCESS_KEY') {|v| access_key_id = v }
  opt.on('-s', '--secret-access-key SECRET_ACCESS_KEY') {|v| secret_access_key = v }
  opt.on('-r', '--region REGION') {|v| endpoint = v }
  opt.parse!

  unless access_key_id and secret_access_key
    puts opt.help
    exit 1
  end
end

unless /\Aec2\.[^.]+\.amazonaws\.com\Z/ =~ endpoint
  endpoint = "ec2.#{endpoint}.amazonaws.com"
end

ec2cli = EC2Client.new(access_key_id, secret_access_key, endpoint)
source = ec2cli.query('DescribeInstances')

# puts source

doc = REXML::Document.new(source)
# docをごにょごにょ

ec2-api-toolsよりも速いのがよい。

*1:OpenSSLは標準ライブラリでいいのかな…