-
-
Save takus/612a52c785dc875557cf to your computer and use it in GitHub Desktop.
Simple script to audit AWS EC2 Reserved Instances vs Instances running.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# Audits your reserved instances vs your running instances, and reports back. | |
# If you want to run this from an AWS instance, its IAM Role must be granted at least the following: | |
# | |
#{ | |
# "Statement": [ | |
# { | |
# "Action": [ | |
# "ec2:DescribeInstances", | |
# "ec2:DescribeReservedInstances", | |
# ], | |
# "Effect": "Allow", | |
# "Resource": "*" | |
# } | |
# ] | |
# } | |
require 'rubygems' | |
require 'trollop' # Option parsing made stupid easy | |
require 'aws-sdk-v1' | |
class Hash | |
# Compares another Hash to this one, and returns a Hash of the differences | |
def deep_diff(b) | |
a = self | |
(a.keys | b.keys).inject({}) do |diff, k| | |
if a[k] != b[k] | |
if a[k].respond_to?(:deep_diff) && b[k].respond_to?(:deep_diff) | |
diff[k] = a[k].deep_diff(b[k]) | |
else | |
diff[k] = [a[k], b[k]] | |
end | |
end | |
diff | |
end | |
end | |
end | |
class AZ | |
attr_accessor :name, :instances, :ris | |
def initialize(name) | |
@name = name | |
@instances = Hash.new | |
@ris = Hash.new | |
end | |
# Add a running instance (I) | |
def add_instance(entry) | |
@instances[entry] = @instances.has_key?(entry) ? @instances[entry] + 1 : 1 | |
end | |
# Add a reserved instance (RI), and an optional number of them | |
def add_ris(entry, ricount = 1) | |
@ris[entry] = @ris.has_key?(entry) ? @ris[entry] + ricount : ricount | |
end | |
# Analyze the current AZ object, outputting relevant differences | |
def analyze(prefix = '', verbose = false) | |
@ris.deep_diff(@instances).sort.each {|key, value| | |
(ri,i) = value | |
ri = 0 if ri.nil? | |
i = 0 if i.nil? | |
d = i - ri | |
if verbose | |
puts ["#{prefix}#{key}", "Reserved Instances: #{ri}", "Instances Running: #{i}", "Difference: #{d}"].join("\t") | |
else | |
puts ["#{prefix}#{key}", "RI: #{ri}", "I: #{i}", "D: #{d}"].join("\t") | |
end | |
} | |
end | |
end | |
# Option parsing via Trollop | |
opts = Trollop::options do | |
opt :region, "AWS Region", | |
:type => String, | |
:default => 'us-east-1' | |
opt :verbose, "Spell out the variables" | |
opt :debug, "Be noisy" | |
end | |
# If you need to deal with keys/credentials, do that here. | |
ec2 = AWS::EC2.new(:region => opts[:region]) | |
AZs = Hash.new | |
AWS.memoize do | |
# Collect active RIs, add them to the appropriate AZ object | |
ec2.reserved_instances.each {|ri| | |
next unless ri.state == 'active' | |
puts "[d] RI id: #{ri.id} #{ri.availability_zone} #{ri.instance_type} #{ri.instance_count}" if opts[:debug] | |
unless AZs.has_key?(ri.availability_zone) | |
AZs[ri.availability_zone] = AZ.new(ri.availability_zone) | |
end | |
AZs[ri.availability_zone].add_ris(ri.instance_type, ri.instance_count) | |
} | |
# Collect running instances, add them to the appropriate AZ object | |
ec2.instances.each {|i| | |
next unless i.status.to_s == 'running' | |
next if i.spot_instance? | |
puts "[d] I id: #{i.id} #{i.availability_zone} #{i.instance_type}" if opts[:debug] | |
unless AZs.has_key?(i.availability_zone) | |
AZs[i.availability_zone] = AZ.new(i.availability_zone) | |
end | |
AZs[i.availability_zone].add_instance(i.instance_type) | |
} | |
end | |
# Analyze each AZ | |
AZs.sort.each {|name, az| | |
puts "\nAZ: #{name}" | |
az.analyze("\t", opts[:verbose]) # we prefix a tab, for show | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment