include Opscode::Aws::Ec2

# Support whyrun
def whyrun_supported?
  true
end

action :associate do
  ip = new_resource.ip || node['aws']['elastic_ip'][new_resource.name]['ip']
  addr = address(ip)

  if addr.nil?
    raise "Elastic IP #{ip} does not exist"
  elsif addr[:instance_id] == instance_id
    Chef::Log.debug("Elastic IP #{ip} is already attached to the instance")
  else
    converge_by("attach Elastic IP #{ip} to the instance") do
      Chef::Log.info("Attaching Elastic IP #{ip} to the instance")
      attach(ip, new_resource.timeout)
      node.set['aws']['elastic_ip'][new_resource.name]['ip'] = ip
      node.save unless Chef::Config[:solo]
    end
  end
end

action :disassociate do
  ip = new_resource.ip || node['aws']['elastic_ip'][new_resource.name]['ip']
  addr = address(ip)

  if addr.nil?
    Chef::Log.debug("Elastic IP #{ip} does not exist, so there is nothing to detach")
  elsif addr[:instance_id] != instance_id
    Chef::Log.debug("Elastic IP #{ip} is already detached from the instance")
  else
    converge_by("detach Elastic IP #{ip} from the instance") do
      Chef::Log.info("Detaching Elastic IP #{ip} from the instance")
      detach(ip, new_resource.timeout)
    end
  end
end

action :allocate do
  current_elastic_ip = node['aws']['elastic_ip'][new_resource.name]['ip']
  if current_elastic_ip
    Chef::Log.info("An Elastic IP was already allocated for #{new_resource.name} #{current_elastic_ip} from the instance")
  else
    converge_by("allocate new Elastic IP for #{new_resource.name}") do
      addr = ec2.allocate_address
      Chef::Log.info("Allocated Elastic IP #{addr[:public_ip]} from the instance")
      node.set['aws']['elastic_ip'][new_resource.name]['ip'] = addr[:public_ip]
      node.save unless Chef::Config[:solo]
    end
  end
end

private

def address(ip)
  ec2.describe_addresses.find { |a| a[:public_ip] == ip }
end

def attach(ip, timeout)
  addr = address(ip)
  if addr[:domain] == 'vpc'
    ec2.associate_address(instance_id, {:allocation_id => addr[:allocation_id]})
  else
    ec2.associate_address(instance_id, {:public_ip => addr[:public_ip]})
  end

  # block until attached
  begin
    Timeout::timeout(timeout) do
      while true
        addr = address(ip)
        if addr.nil?
          raise "Elastic IP has been deleted while waiting for attachment"
        elsif addr[:instance_id] == instance_id
          Chef::Log.debug("Elastic IP is attached to this instance")
          break
        else
          Chef::Log.debug("Elastic IP is currently attached to #{addr[:instance_id]}")
        end
        sleep 3
      end
    end
  rescue Timeout::Error
    raise "Timed out waiting for attachment after #{timeout} seconds"
  end
end

def detach(ip, timeout)
  ec2.disassociate_address({:public_ip => ip})

  # block until detached
  begin
    Timeout::timeout(timeout) do
      while true
        addr = address(ip)
        if addr.nil?
          Chef::Log.debug("Elastic IP has been deleted while waiting for detachment")
        elsif addr[:instance_id] != instance_id
          Chef::Log.debug("Elastic IP is detached from this instance")
          break
        else
          Chef::Log.debug("Elastic IP is still attached")
        end
        sleep 3
      end
    end
  rescue Timeout::Error
    raise "Timed out waiting for detachment after #{timeout} seconds"
  end
end
