class AwsAmiCleanup::CleanupAmis

Constants

DEFAULT_NUMBER_OF_AMIS_TO_KEEP

Attributes

number_of_amis_to_keep[RW]
region[RW]
skip_image_under_use_verification[RW]

Public Class Methods

new(region, number_of_amis_to_keep, skip_image_under_use_verification) click to toggle source
# File lib/aws_ami_cleanup/cleanup_amis.rb, line 12
def initialize(region, number_of_amis_to_keep, skip_image_under_use_verification)
  @region = region

  @number_of_amis_to_keep = number_of_amis_to_keep || DEFAULT_NUMBER_OF_AMIS_TO_KEEP
  if number_of_amis_to_keep <= 0
    raise 'Number of AMIs to keep must be higher than 0.'
  end

  @skip_image_under_use_verification = skip_image_under_use_verification
end

Public Instance Methods

execute!(ami_name:, ami_owner:, dry_run:) click to toggle source
# File lib/aws_ami_cleanup/cleanup_amis.rb, line 23
def execute!(ami_name:, ami_owner:, dry_run:)
  puts "RUNNING IN DRY MODE." if dry_run

  potential_amis_to_remove = amis(ami_name, ami_owner)
  ami_ids = potential_amis_to_remove.collect(&:image_id)

  if skip_image_under_use_verification
    ami_ids_to_remove = ami_ids
  else
    ami_ids_to_remove = ami_ids - amis_in_use
  end

  potential_amis_to_remove.keep_if {|a| ami_ids_to_remove.include?(a.image_id) }

  if potential_amis_to_remove.count > number_of_amis_to_keep
    amis_to_remove = potential_amis_to_remove[number_of_amis_to_keep..-1]
    amis_to_keep = potential_amis_to_remove[0..(number_of_amis_to_keep-1)]

    puts "Deregistering old AMIs..."

    amis_to_remove.each do |ami|
      ebs_mappings = ami.block_device_mappings
      puts "Deregistering #{ami.image_id}"

      begin
        ec2.deregister_image(image_id: ami.image_id, dry_run: dry_run)
      rescue Aws::EC2::Errors::DryRunOperation
        # When running in dry mode, EC2 raises this exception if operation would have succeeded, we catch them so the full process can run
      end

      delete_ami_snapshots(ebs_mappings, dry_run)
    end

    puts "Currently active AMIs..."
    amis_to_keep.each do |ami|
      puts ami.image_id
    end
  else
    puts "no AMIs to clean."
  end
end

Protected Instance Methods

amis(ami_name, ami_owner) click to toggle source
# File lib/aws_ami_cleanup/cleanup_amis.rb, line 75
def amis(ami_name, ami_owner)
  return @__amis unless @__amis.nil?

  # Cannot lookup by Name tag because that's only available from the owner account.
  describe_images_params = { owners: [ ami_owner ] }
  all_images_from_owner = ec2.describe_images(describe_images_params).images
  name_matching_images = all_images_from_owner.filter {|i| i.name.match?(ami_name) }

  @__amis = sort_by_created_at(name_matching_images)
end
amis_in_use() click to toggle source
# File lib/aws_ami_cleanup/cleanup_amis.rb, line 91
def amis_in_use
  image_ids = []

  autoscaling_groups = auto_scaling.describe_auto_scaling_groups.auto_scaling_groups

  # Find AMIs used by auto scaling groups with launch templates
  launch_template_ids = autoscaling_groups.reject {|asg| asg.launch_template.nil? }
                                          .collect {|asg| asg.launch_template.launch_template_id }
  launch_template_ids.each do |launch_template_id|
    image_ids << ec2.describe_launch_template_versions(launch_template_id: launch_template_id, max_results: 1)
                    .launch_template_versions
                    .first
                    .launch_template_data
                    .image_id
  end

  # Find AMIs used by auto scaling groups with launch configurations
  launch_configuration_names = autoscaling_groups.filter {|asg| asg.launch_template.nil? }
                                                 .collect {|asg| asg.launch_configuration_name }
  launch_configurations = auto_scaling.describe_launch_configurations(launch_configuration_names: launch_configuration_names).launch_configurations
  image_ids += launch_configurations.map(&:image_id)

  # Finally, find AMIs used by instances not belonging to auto scaling groups
  ec2_reservations = ec2.describe_instances
  image_ids += ec2_reservations.reservations.collect {|res| res.instances.map(&:image_id) }.flatten

  image_ids.flatten
end
auto_scaling() click to toggle source
# File lib/aws_ami_cleanup/cleanup_amis.rb, line 71
def auto_scaling
  @__auto_scaling ||= Aws::AutoScaling::Client.new(region: region)
end
delete_ami_snapshots(ebs_mappings, dry_run) click to toggle source
# File lib/aws_ami_cleanup/cleanup_amis.rb, line 120
def delete_ami_snapshots(ebs_mappings, dry_run)
  ebs_mappings.each do |ebs_mapping|
    # Skip ephimeral block devices
    next if ebs_mapping.ebs.nil? || ebs_mapping.ebs.snapshot_id.nil?

    snapshot_id = ebs_mapping.ebs.snapshot_id
    puts "Deleting snapshot #{snapshot_id}"
    begin
      ec2.delete_snapshot(snapshot_id: snapshot_id, dry_run: dry_run)
    rescue Aws::EC2::Errors::DryRunOperation
      # When running in dry mode, EC2 raises this exception if operation would have succeeded, we catch them so the full process can run
    end
  end
end
ec2() click to toggle source
# File lib/aws_ami_cleanup/cleanup_amis.rb, line 67
def ec2
  @__ec2 ||= Aws::EC2::Client.new(region: region)
end
sort_by_created_at(collection) click to toggle source
# File lib/aws_ami_cleanup/cleanup_amis.rb, line 86
def sort_by_created_at(collection)
  # Returns items oredered by creation_date from newer to older
  collection.sort {|a, b| DateTime.parse(b.creation_date) <=> DateTime.parse(a.creation_date) }
end