Skip to content

Commit

Permalink
Use aws-sdk v 2
Browse files Browse the repository at this point in the history
  • Loading branch information
ubiquitousthey committed Nov 21, 2014
1 parent 4db75a0 commit 2a183fd
Show file tree
Hide file tree
Showing 14 changed files with 130 additions and 108 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ bin
.kitchen.local.yml
.coverage
tmp
.kitchen/
2 changes: 2 additions & 0 deletions .kitchen.cloud.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ suites:
aws_test:
key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %>
access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %>
bucket: <%= ENV['AWS_S3_BUCKET'] || 'aws-test' %>
s3key: <%= ENV['AWS_S3_KEY'] || 'an_file' %>

- name: instance_monitoring
run_list:
Expand Down
33 changes: 11 additions & 22 deletions libraries/ec2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ module Ec2
def find_snapshot_id(volume_id="", find_most_recent=false)
snapshot_id = nil
snapshots = if find_most_recent
ec2.describe_snapshots.sort { |a,b| a[:aws_started_at] <=> b[:aws_started_at] }
ec2.describe_snapshots.sort { |a,b| a[:start_time] <=> b[:start_time] }
else
ec2.describe_snapshots.sort { |a,b| b[:aws_started_at] <=> a[:aws_started_at] }
ec2.describe_snapshots.sort { |a,b| b[:start_time] <=> a[:start_time] }
end
snapshots.each do |snapshot|
if snapshot[:aws_volume_id] == volume_id
snapshot_id = snapshot[:aws_id]
if snapshot[:volume_id] == volume_id
snapshot_id = snapshot[:snapshot_id]
end
end
raise "Cannot find snapshot id!" unless snapshot_id
Expand All @@ -40,7 +40,7 @@ def find_snapshot_id(volume_id="", find_most_recent=false)
end

def ec2
@@ec2 ||= create_aws_interface(RightAws::Ec2)
@@ec2 ||= create_aws_interface(::Aws::EC2::Client)
end

def instance_id
Expand All @@ -55,32 +55,21 @@ def instance_availability_zone

def create_aws_interface(aws_interface)
begin
require 'right_aws'
require 'aws-sdk'
rescue LoadError
Chef::Log.error("Missing gem 'right_aws'. Use the default aws recipe to install it first.")
end

region = instance_availability_zone
region = region[0, region.length-1]

if new_resource.aws_access_key and new_resource.aws_secret_access_key
aws_interface.new(new_resource.aws_access_key, new_resource.aws_secret_access_key, {:logger => Chef::Log, :region => region})
if !new_resource.aws_access_key.to_s.empty? and !new_resource.aws_secret_access_key.to_s.empty?
creds = ::Aws::Credentials.new(new_resource.aws_access_key, new_resource.aws_secret_access_key)
else
creds = query_role_credentials
aws_interface.new(creds['AccessKeyId'], creds['SecretAccessKey'], {:logger => Chef::Log, :region => region, :token => creds['Token']})
Chef::Log.info("Attempting to use iam profile")
creds = ::Aws::InstanceProfileCredentials.new
end
end

def query_role
r = open("http://169.254.169.254/latest/meta-data/iam/security-credentials/").readlines.first
r
end

def query_role_credentials(role = query_role)
fail "Instance has no IAM role." if role.to_s.empty?
creds = open("http://169.254.169.254/latest/meta-data/iam/security-credentials/#{role}",options = {:proxy => false}){|f| JSON.parse(f.string)}
Chef::Log.debug("Retrieved instance credentials for IAM role #{role}")
creds
aws_interface.new(:credentials => creds ,:region => region)
end

def query_instance_id
Expand Down
2 changes: 1 addition & 1 deletion libraries/elb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module Elb
include Opscode::Aws::Ec2

def elb
@@elb ||= create_aws_interface(RightAws::ElbInterface)
@@elb ||= create_aws_interface(::Aws::ElasticLoadBalancing::Client)
end
end
end
Expand Down
13 changes: 13 additions & 0 deletions libraries/s3.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
require File.join(File.dirname(__FILE__), 'ec2')

module Opscode
module Aws
module S3
include Opscode::Aws::Ec2

def s3
@@s3 ||= create_aws_interface(::Aws::S3::Client)
end
end
end
end
112 changes: 60 additions & 52 deletions providers/ebs_volume.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def whyrun_supported?
if nvid
# volume id is registered in the node data, so check that the volume in fact exists in EC2
vol = volume_by_id(nvid)
exists = vol && vol[:aws_status] != "deleting"
exists = vol && vol[:state] != "deleting"
# TODO: determine whether this should be an error or just cause a new volume to be created. Currently erring on the side of failing loudly
raise "Volume with id #{nvid} is registered with the node but does not exist in EC2. To clear this error, remove the ['aws']['ebs_volume']['#{new_resource.name}']['volume_id'] entry from this node's data." unless exists
else
Expand All @@ -26,10 +26,10 @@ def whyrun_supported?
if new_resource.device && (attached_volume = currently_attached_volume(instance_id, new_resource.device))
Chef::Log.debug("There is already a volume attached at device #{new_resource.device}")
compatible = volume_compatible_with_resource_definition?(attached_volume)
raise "Volume #{attached_volume[:aws_id]} attached at #{attached_volume[:aws_device]} but does not conform to this resource's specifications" unless compatible
raise "Volume #{attached_volume[:volume_id]} attached at #{attached_volume[:aws_device]} but does not conform to this resource's specifications" unless compatible
Chef::Log.debug("The volume matches the resource's definition, so the volume is assumed to be already created")
converge_by("update the node data with volume id: #{attached_volume[:aws_id]}") do
node.set['aws']['ebs_volume'][new_resource.name]['volume_id'] = attached_volume[:aws_id]
converge_by("update the node data with volume id: #{attached_volume[:volume_id]}") do
node.set['aws']['ebs_volume'][new_resource.name]['volume_id'] = attached_volume[:volume_id]
node.save unless Chef::Config[:solo]
end
else
Expand All @@ -53,53 +53,56 @@ def whyrun_supported?
# symbols, not strings.
vol = determine_volume

if vol[:aws_status] == "in-use"
if vol[:aws_instance_id] != instance_id
raise "Volume with id #{vol[:aws_id]} exists but is attached to instance #{vol[:aws_instance_id]}"
else
Chef::Log.debug("Volume is already attached")
if vol[:state] == "in-use"
Chef::Log.info("Vol: #{vol}")
vol[:attachments].each do |attachment|
if attachment[:instance_id] != instance_id
raise "Volume with id #{vol[:volume_id]} exists but is attached to instance #{attachment[:instance_id]}"
else
Chef::Log.debug("Volume is already attached")
end
end
else
converge_by("attach the volume with aws_id=#{vol[:aws_id]} id=#{instance_id} device=#{new_resource.device} and update the node data with created volume's id") do
converge_by("attach the volume with aws_id=#{vol[:volume_id]} id=#{instance_id} device=#{new_resource.device} and update the node data with created volume's id") do
# attach the volume and register its id in the node data
attach_volume(vol[:aws_id], instance_id, new_resource.device, new_resource.timeout)
attach_volume(vol[:volume_id], instance_id, new_resource.device, new_resource.timeout)
# always use a symbol here, it is a Hash
node.set['aws']['ebs_volume'][new_resource.name]['volume_id'] = vol[:aws_id]
node.set['aws']['ebs_volume'][new_resource.name]['volume_id'] = vol[:volume_id]
node.save unless Chef::Config[:solo]
end
end
end

action :detach do
vol = determine_volume
converge_by("detach volume with id: #{vol[:aws_id]}") do
detach_volume(vol[:aws_id], new_resource.timeout)
converge_by("detach volume with id: #{vol[:volume_id]}") do
detach_volume(vol[:volume_id], new_resource.timeout)
end
end

action :snapshot do
vol = determine_volume
converge_by("would create a snapshot for volume: #{vol[:aws_id]}") do
snapshot = ec2.create_snapshot(vol[:aws_id],new_resource.description)
Chef::Log.info("Created snapshot of #{vol[:aws_id]} as #{snapshot[:aws_id]}")
converge_by("would create a snapshot for volume: #{vol[:volume_id]}") do
snapshot = ec2.create_snapshot(volume_id: vol[:volume_id],description: new_resource.description)
Chef::Log.info("Created snapshot of #{vol[:volume_id]} as #{snapshot[:volume_id]}")
end
end

action :prune do
vol = determine_volume
old_snapshots = Array.new
Chef::Log.info "Checking for old snapshots"
ec2.describe_snapshots.sort { |a,b| b[:aws_started_at] <=> a[:aws_started_at] }.each do |snapshot|
if snapshot[:aws_volume_id] == vol[:aws_id]
Chef::Log.info "Found old snapshot #{snapshot[:aws_id]} (#{snapshot[:aws_volume_id]}) #{snapshot[:aws_started_at]}"
ec2.describe_snapshots[:snapshots].sort { |a,b| b[:start_time] <=> a[:start_time] }.each do |snapshot|
if snapshot[:volume_id] == vol[:volume_id]
Chef::Log.info "Found old snapshot #{snapshot[:volume_id]} (#{snapshot[:volume_id]}) #{snapshot[:start_time]}"
old_snapshots << snapshot
end
end
if old_snapshots.length > new_resource.snapshots_to_keep
old_snapshots[new_resource.snapshots_to_keep, old_snapshots.length].each do |die|
converge_by("delete snapshot with id: #{die[:aws_id]}") do
Chef::Log.info "Deleting old snapshot #{die[:aws_id]}"
ec2.delete_snapshot(die[:aws_id])
converge_by("delete snapshot with id: #{die[:snapshot_id]}") do
Chef::Log.info "Deleting old snapshot #{die[:snapshot_id]}"
ec2.delete_snapshot(snapshot_id: die[:snapshot_id])
end
end
end
Expand All @@ -118,7 +121,7 @@ def volume_id_in_node_data
# Pulls the volume id from the volume_id attribute or the node data and verifies that the volume actually exists
def determine_volume
vol = currently_attached_volume(instance_id, new_resource.device)
vol_id = new_resource.volume_id || volume_id_in_node_data || ( vol ? vol[:aws_id] : nil )
vol_id = new_resource.volume_id || volume_id_in_node_data || ( vol ? vol[:volume_id] : nil )
raise "volume_id attribute not set and no volume id is set in the node data for this resource (which is populated by action :create) and no volume is attached at the device" unless vol_id

# check that volume exists
Expand All @@ -130,20 +133,22 @@ def determine_volume

# Retrieves information for a volume
def volume_by_id(volume_id)
ec2.describe_volumes.find{|v| v[:aws_id] == volume_id}
ec2.describe_volumes[:volumes].find{|v| v[:volume_id] == volume_id}
end

# Returns the volume that's attached to the instance at the given device or nil if none matches
def currently_attached_volume(instance_id, device)
ec2.describe_volumes.find{|v| v[:aws_instance_id] == instance_id && v[:aws_device] == device}
ec2.describe_volumes[:volumes].find do |v|
v[:attachments].any? {|a| a[:device] == device && a[:instance_id] == instance_id}
end
end

# Returns true if the given volume meets the resource's attributes
def volume_compatible_with_resource_definition?(volume)
if new_resource.snapshot_id =~ /vol/
new_resource.snapshot_id(find_snapshot_id(new_resource.snapshot_id, new_resource.most_recent_snapshot))
end
(new_resource.size.nil? || new_resource.size == volume[:aws_size]) &&
(new_resource.size.nil? || new_resource.size == volume[:size]) &&
(new_resource.availability_zone.nil? || new_resource.availability_zone == volume[:zone]) &&
(new_resource.snapshot_id.nil? || new_resource.snapshot_id == volume[:snapshot_id])
end
Expand All @@ -155,68 +160,73 @@ def create_volume(snapshot_id, size, availability_zone, timeout, volume_type, pi
# Sanity checks so we don't shoot ourselves.
raise "Invalid volume type: #{volume_type}" unless ['standard', 'io1', 'gp2'].include?(volume_type)

params = {availability_zone: availability_zone, volume_type: volume_type}
# PIOPs requested. Must specify an iops param and probably won't be "low".
if volume_type == 'io1'
raise 'IOPS value not specified.' unless piops >= 100
params[:iops] = piops
end

# Shouldn't see non-zero piops param without appropriate type.
if piops > 0
raise 'IOPS param without piops volume type.' unless volume_type == 'io1'
end

create_volume_opts = { :volume_type => volume_type }
# TODO: this may have to be casted to a string. rightaws vs aws doc discrepancy.
create_volume_opts[:iops] = piops if volume_type == 'io1'
if snapshot_id
params[:snapshot_id] = snapshot_id
else
params[:size] = size
end

nv = ec2.create_volume(snapshot_id, size, availability_zone, create_volume_opts)
Chef::Log.debug("Created new volume #{nv[:aws_id]}#{snapshot_id ? " based on #{snapshot_id}" : ""}")
nv = ec2.create_volume(params)
Chef::Log.debug("Created new volume #{nv[:volume_id]}#{snapshot_id ? " based on #{snapshot_id}" : ""}")

# block until created
begin
Timeout::timeout(timeout) do
while true
vol = volume_by_id(nv[:aws_id])
if vol && vol[:aws_status] != "deleting"
if ["in-use", "available"].include?(vol[:aws_status])
Chef::Log.info("Volume #{nv[:aws_id]} is available")
vol = volume_by_id(nv[:volume_id])
if vol && vol[:state] != "deleting"
if ["in-use", "available"].include?(vol[:state])
Chef::Log.info("Volume #{nv[:volume_id]} is available")
break
else
Chef::Log.debug("Volume is #{vol[:aws_status]}")
Chef::Log.debug("Volume is #{vol[:state]}")
end
sleep 3
else
raise "Volume #{nv[:aws_id]} no longer exists"
raise "Volume #{nv[:volume_id]} no longer exists"
end
end
end
rescue Timeout::Error
raise "Timed out waiting for volume creation after #{timeout} seconds"
end

nv[:aws_id]
nv[:volume_id]
end

# Attaches the volume and blocks until done (or times out)
def attach_volume(volume_id, instance_id, device, timeout)
Chef::Log.debug("Attaching #{volume_id} as #{device}")
ec2.attach_volume(volume_id, instance_id, device)
ec2.attach_volume(volume_id: volume_id, instance_id: instance_id, device: device)

# block until attached
begin
Timeout::timeout(timeout) do
while true
vol = volume_by_id(volume_id)
if vol && vol[:aws_status] != "deleting"
if vol[:aws_attachment_status] == "attached"
if vol[:aws_instance_id] == instance_id
if vol && vol[:state] != "deleting"
attachment = vol[:attachments].find{|a| a[:state] == "attached"}
if attachment != nil
if attachment[:instance_id] == instance_id
Chef::Log.info("Volume #{volume_id} is attached to #{instance_id}")
break
else
raise "Volume is attached to instance #{vol[:aws_instance_id]} instead of #{instance_id}"
end
else
Chef::Log.debug("Volume is #{vol[:aws_status]}")
Chef::Log.debug("Volume is #{vol[:state]}")
end
sleep 3
else
Expand All @@ -232,21 +242,21 @@ def attach_volume(volume_id, instance_id, device, timeout)
# Detaches the volume and blocks until done (or times out)
def detach_volume(volume_id, timeout)
vol = volume_by_id(volume_id)
if vol[:aws_instance_id] != instance_id
Chef::Log.debug("EBS Volume #{volume_id} is not attached to this instance (attached to #{vol[:aws_instance_id]}). Skipping...")
if vol[:instance_id] != instance_id
Chef::Log.debug("EBS Volume #{volume_id} is not attached to this instance (attached to #{vol[:instance_id]}). Skipping...")
return
end
Chef::Log.debug("Detaching #{volume_id}")
orig_instance_id = vol[:aws_instance_id]
ec2.detach_volume(volume_id)
orig_instance_id = vol[:instance_id]
ec2.detach_volume(volume_id: volume_id)

# block until detached
begin
Timeout::timeout(timeout) do
while true
vol = volume_by_id(volume_id)
if vol && vol[:aws_status] != "deleting"
if vol[:aws_instance_id] != orig_instance_id
if vol && vol[:state] != "deleting"
if vol[:instance_id] != orig_instance_id
Chef::Log.info("Volume detached from #{orig_instance_id}")
break
else
Expand All @@ -263,5 +273,3 @@ def detach_volume(volume_id, timeout)
raise "Timed out waiting for volume detachment after #{timeout} seconds"
end
end


Loading

0 comments on commit 2a183fd

Please sign in to comment.