module AwsPocketknife::Ec2

Constants

DELAY_SECONDS
Logging

include AwsPocketknife::Common::Logging

MAX_ATTEMPTS
STATE_AVAILABLE
STATE_DEREGISTERED
STATE_ERROR
STATE_FAILED
STATE_INVALID
STATE_PENDING

Public Class Methods

clean_ami(options) click to toggle source
# File lib/aws_pocketknife/ec2.rb, line 74
def clean_ami(options)
  Logging.info "options: #{options}"

  dry_run = options.fetch(:dry_run, true)
  Logging.info "Finding AMIs by creation time"
  image_ids = find_ami_by_creation_time(options)
  Logging.info "Done. Finding unusued AMIs now..."
  images_to_delete = find_unused_ami(image_ids: image_ids)

  Logging.info "images (#{image_ids.length}): #{image_ids}"
  Logging.info "images to delete (#{images_to_delete.length}): #{images_to_delete}"

  unless dry_run
    images_to_delete.each do |image_id|
      delete_ami_by_id(id: image_id)
    end
  end

end
create_image(instance_id: "", name: "", description: "Created at click to toggle source
# File lib/aws_pocketknife/ec2.rb, line 148
def create_image(instance_id: "", name: "", description: "Created at #{Time.now}",
                 timeout: 1800, publish_to_account: "",
                 volume_type: "gp2",
                 iops: 3,
                 encrypted: false,
                 volume_size: 60
)

  begin
    Logging.info "creating image"
    instance = find_by_id(instance_id: instance_id)
    instance = ec2.instances[instance_id]
    image = instance.create_image(name, :description => description)
    sleep 2 until image.exists?
    Logging.info "image #{image.id} state: #{image.state}"
    sleep 10 until image.state != :pending
    if image.state == :failed
      raise "Create image failed"
    end
    Logging.info "image created"
  rescue => e
    Logging.error "Creating AMI failed #{e.message}"
    Logging.error e.backtrace.join("\n")
    raise e
  end
  if publish_to_account.length != 0
    Logging.info "add permissions for #{publish_to_account}"
    image.permissions.add(publish_to_account.gsub(/-/, ''))
  end
  image.id.tap do |image_id|
    Logging.info "Image #{@name}[#{image_id}] created"
    return image_id
  end
end
delete_ami_by_id(id: '') click to toggle source
# File lib/aws_pocketknife/ec2.rb, line 41
def delete_ami_by_id(id: '')
  Logging.info "deleting image #{id}"
  image = find_ami_by_id(id: id)
  snapshot_ids = snapshot_ids(image)
  ec2_client.deregister_image(image_id: id)

  Retryable.retryable(:tries => 20, :sleep => lambda { |n| 2**n }, :on => StandardError) do |retries, exception|
    image = find_ami_by_id(id: id)
    message =  "retry #{retries} - Deleting image #{id}"
    message << " State: #{image.state}" if image
    Logging.info message
    raise StandardError unless image.nil?
  end

  delete_snapshots(snapshot_ids: snapshot_ids)
end
delete_snapshots(snapshot_ids: []) click to toggle source
# File lib/aws_pocketknife/ec2.rb, line 58
def delete_snapshots(snapshot_ids: [])
  snapshot_ids.each do |snapshot_id|
    Logging.info "Deleting Snapshot: #{snapshot_id}"
    ec2_client.delete_snapshot(snapshot_id: snapshot_id)
  end
end
describe_instances_by_image_id(image_id_list: []) click to toggle source
# File lib/aws_pocketknife/ec2.rb, line 215
def describe_instances_by_image_id(image_id_list: [])
  instances = []
  resp = ec2_client.describe_instances({dry_run: false,
                                         filters: [
                                             {
                                                 name: "image-id",
                                                 values: image_id_list
                                             }
                                         ]})
  resp.reservations.each do |reservation|
    reservation.instances.each do |instance|
      instances << instance
    end
  end
  instances
end
find_ami_by_creation_time(options) click to toggle source
# File lib/aws_pocketknife/ec2.rb, line 110
def find_ami_by_creation_time(options)

  days = options.fetch(:days, '30').to_i * 24 * 3600
  creation_time = Time.now-days
  Logging.info "Cleaning up images older than #{days} days, i.e, with creation_time < #{creation_time})"

  image_ids = []
  images = find_ami_by_name(name: options.fetch(:ami_name_pattern, ''))
  images.each do |image|
    image_creation_time = Time.parse(image.creation_date)
    msg = "image #{image.name} (#{image.image_id}) (image_creation_time: #{image_creation_time}) < (#{creation_time}) ? "
    if image_creation_time <= creation_time
      image_ids << image.image_id
      msg << "YES, marking to be deleted"
    else
      msg << "NO"
    end
    Logging.info msg
  end
  Logging.info "Done reading AMIs"
  return image_ids
end
find_ami_by_id(id: '') click to toggle source
# File lib/aws_pocketknife/ec2.rb, line 36
def find_ami_by_id(id: '')
  ec2_client.describe_images({dry_run: false,
                              image_ids: [id]}).images.first
end
find_ami_by_name(name: '') click to toggle source
# File lib/aws_pocketknife/ec2.rb, line 26
def find_ami_by_name(name: '')
  ec2_client.describe_images({dry_run: false,
                              filters: [
                                  {
                                      name: "tag:Name",
                                      values: [name]
                                  }
                              ]}).images
end
find_by_id(instance_id: "") click to toggle source
# File lib/aws_pocketknife/ec2.rb, line 232
def find_by_id(instance_id: "")
  resp = ec2_client.describe_instances({dry_run: false, instance_ids: [instance_id.to_s]})
  if resp.nil? or resp.reservations.length == 0 or resp.reservations[0].instances.length == 0
    return nil
  else
    return resp.reservations.first.instances.first
  end
end
find_by_name(name: "") click to toggle source

serverfault.com/questions/560337/search-ec2-instance-by-its-name-from-aws-command-line-tool

# File lib/aws_pocketknife/ec2.rb, line 198
def find_by_name(name: "")
  instances = []
  resp = ec2_client.describe_instances({dry_run: false,
                                        filters: [
                                            {
                                                name: "tag:Name",
                                                values: [name]
                                            }
                                        ]})
  resp.reservations.each do |reservation|
    reservation.instances.each do |instance|
      instances << instance
    end
  end
  instances
end
find_unused_ami(image_ids: []) click to toggle source
# File lib/aws_pocketknife/ec2.rb, line 94
def find_unused_ami(image_ids: [])
  images_to_delete = []
  image_ids.each do |image_id|
    # check if there is any instance using the image id
    Logging.info "Checking if #{image_id} can be deleted..."
    instances = describe_instances_by_image_id(image_id_list: [image_id])
    if instances.empty?
      images_to_delete << image_id
    else
      Logging.info "#{image_id} is used by instance #{instances.map { |instance| instance.instance_id }}"
    end
    Kernel.sleep 2
  end
  return images_to_delete
end
get_windows_password(instance_id: "") click to toggle source
# File lib/aws_pocketknife/ec2.rb, line 241
def get_windows_password(instance_id: "")

  private_keyfile_dir = ENV["AWS_POCKETKNIFE_KEYFILE_DIR"] || ""
  raise "Environment variable AWS_POCKETKNIFE_KEYFILE_DIR is not defined" if private_keyfile_dir.length == 0

  instance = find_by_id(instance_id: instance_id)
  key_name = instance.key_name
  private_keyfile = File.join(private_keyfile_dir, "#{key_name}.pem")
  raise "File #{private_keyfile} not found" unless File.exist?(private_keyfile)

  resp = ec2_client.get_password_data({dry_run: false,
                                        instance_id: instance_id})
  encrypted_password = resp.password_data
  decrypted_password = decrypt_windows_password(encrypted_password, private_keyfile)

  RecursiveOpenStruct.new({password: decrypted_password,
                          instance_id: instance.instance_id,
                           private_ip_address: instance.private_ip_address,
                           public_ip_address: instance.public_ip_address}, recurse_over_arrays: true)
end
share_ami(image_id: '', user_id: '', options: {}) click to toggle source
# File lib/aws_pocketknife/ec2.rb, line 133
def share_ami(image_id: '', user_id: '', options: {})
  begin
    options = {}
    options[:image_id] = image_id
    options[:launch_permission] = create_launch_permission(user_id)
    Logging.info "Sharing Image #{image_id} with #{user_id} with options #{options}"
    response = @ec2_client.modify_image_attribute(options=options)
    return response
  rescue Exception => e
    Logging.error "## Got an error when sharing the image... #{e.cause} -> #{e.message}"
    raise
  end
end
snapshot_ids(image) click to toggle source
# File lib/aws_pocketknife/ec2.rb, line 65
def snapshot_ids(image)
  snapshot_ids = []
  image.block_device_mappings.each do |device_mapping|
    ebs = device_mapping.ebs
    snapshot_ids << ebs.snapshot_id if ebs && !ebs.snapshot_id.to_s.empty?
  end
  snapshot_ids
end
start_instance_by_id(instance_ids) click to toggle source
# File lib/aws_pocketknife/ec2.rb, line 191
def start_instance_by_id(instance_ids)
  instance_id_list = get_instance_id_list(instance_ids: instance_ids)
  Logging.info "Start instance id: #{instance_id_list}"
  ec2_client.start_instances({ instance_ids: instance_id_list })
end
stop_instance_by_id(instance_ids) click to toggle source
# File lib/aws_pocketknife/ec2.rb, line 183
def stop_instance_by_id(instance_ids)
  instance_id_list = get_instance_id_list(instance_ids: instance_ids)
  Logging.info "Stoping instance id: #{instance_id_list}"
  resp = ec2_client.stop_instances({ instance_ids: instance_id_list })
  wait_till_instance_is_stopped(instance_id_list, max_attempts: MAX_ATTEMPTS, delay_seconds: DELAY_SECONDS)
  Logging.info "Stopped ec2 instance #{instance_id_list}"
end

Private Class Methods

create_launch_permission(user_id) click to toggle source

def ec2

@ec2 ||= Aws::EC2.new(:ec2_endpoint => "ec2.#{AwsPocketknife::AWS_REGION}.amazonaws.com")

end

# File lib/aws_pocketknife/ec2.rb, line 268
def create_launch_permission(user_id)
  {
      add: [
          {
              user_id: user_id
          },
      ]
  }
end
decrypt_windows_password(encrypted_password, private_keyfile) click to toggle source

Decrypts an encrypted password using a provided RSA private key file (PEM-format).

# File lib/aws_pocketknife/ec2.rb, line 280
def decrypt_windows_password(encrypted_password, private_keyfile)
  encrypted_password_bytes = Base64.decode64(encrypted_password)
  private_keydata = File.open(private_keyfile, "r").read
  private_key = OpenSSL::PKey::RSA.new(private_keydata)
  private_key.private_decrypt(encrypted_password_bytes)
end
get_instance_id_list(instance_ids: "") click to toggle source
# File lib/aws_pocketknife/ec2.rb, line 287
def get_instance_id_list(instance_ids: "")
  instance_ids.strip.split(";")
end
wait_till_instance_is_stopped(instance_ids, max_attempts: 12, delay_seconds: 10) click to toggle source
# File lib/aws_pocketknife/ec2.rb, line 291
def wait_till_instance_is_stopped(instance_ids, max_attempts: 12, delay_seconds: 10)
  total_wait_seconds = max_attempts * delay_seconds;
  Logging.info "Waiting up to #{total_wait_seconds} seconds with #{delay_seconds} seconds delay for ec2 instance #{instance_ids} to be stopped"
  ec2_client.wait_until(:instance_stopped, { instance_ids: instance_ids }) do |w|
    w.max_attempts = max_attempts
    w.delay = delay_seconds
  end
end
wait_till_instance_is_terminated(instance_ids, max_attempts: 12, delay_seconds: 10) click to toggle source
# File lib/aws_pocketknife/ec2.rb, line 300
def wait_till_instance_is_terminated(instance_ids, max_attempts: 12, delay_seconds: 10)
  total_wait_seconds = max_attempts * delay_seconds;
  Logging.info "Waiting up to #{total_wait_seconds} seconds with #{delay_seconds} seconds delay for ec2 instance #{instance_ids} to be terminated"
  ec2_client.wait_until(:instance_terminated, { instance_ids: instance_ids }) do |w|
    w.max_attempts = max_attempts
    w.delay = delay_seconds
  end
end