aws_ami_cleanup 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: db5bf4d59de51127ee6dab8a547cce124a734dd1c628096e59509ea21dd572d4
4
+ data.tar.gz: 5a6aa3840422b64a71be65ce11ac2a290e4e46a2e5f8730865537ce8676c3368
5
+ SHA512:
6
+ metadata.gz: 8f6000074b50a25f566478022a78a341b676a0fa10eb8cc7945043889035bf5b0b080bac0eede9748ce1886db13c0f92745f0fe2f0abe254acc2a80d93f95f16
7
+ data.tar.gz: 212429abd100adba8cf1be8cbda5a322237a5114ffba2114b3266bce54e1be56d5f1a1dad184f1d2a5245b9713f039063ab2c33f6359a3d506f1d0b3769fadf3
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ *.gem
2
+ .byebug_history
3
+ Gemfile.lock
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ aws_ami_cleanup
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-2.6.6
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
data/README.md ADDED
@@ -0,0 +1,20 @@
1
+ # AWS AMI Cleanup
2
+
3
+ This gem provides the `cleanup_amis` script that allows deregistering unused AMIs. The IAM user running the command must have at the very least the following permissions:
4
+
5
+ - ec2:DescribeImages
6
+ - ec2:DescribeLaunchTemplateVersions
7
+ - ec2:DescribeInstances
8
+ - ec2:DeregisterImage
9
+ - ec2:DeleteSnapshot
10
+ - autoscaling:DescribeAutoScalingGroups
11
+
12
+ Script should be invoked as follows:
13
+
14
+ ```
15
+ cleanup_amis clean_amis --ami_name 'my-ami' --ami_owner 'self'
16
+ ```
17
+
18
+ Where `ami_owner` can be a combination of AWS account IDs, `self`, `amazon`, and `aws-marketplace`.
19
+
20
+ Additionally you can provide the `number_of_amis_to_keep` argument to specify how many AMIs to keep (default is 3) and `region` for the AWS region (default is `us-east-1`).
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path("../lib", __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require "aws_ami_cleanup/version"
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = "aws_ami_cleanup".freeze
9
+ s.version = AwsAmiCleanup::VERSION
10
+
11
+ s.authors = ["Diego Marcet"]
12
+ s.date = "2021-02-19"
13
+ s.summary = "Script for deleting obsolete AMIs"
14
+ s.email = "diego@controlshiftlabs.com"
15
+ s.executables = ["cleanup_amis"].freeze
16
+ s.require_paths = ["lib"]
17
+ s.homepage = "http://github.com/controlshift/aws_ami_cleanup"
18
+ s.license = "MIT"
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ s.files = Dir.chdir(File.expand_path('..', __FILE__)) do
23
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
24
+ end
25
+
26
+ s.add_runtime_dependency "aws-sdk-ec2", "~> 1"
27
+ s.add_runtime_dependency "aws-sdk-autoscaling", "~> 1"
28
+ s.add_runtime_dependency "thor", "~> 1"
29
+ s.add_development_dependency("byebug", "~> 11")
30
+ end
data/bin/cleanup_amis ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ lib = File.expand_path("../../lib", __FILE__)
5
+
6
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
7
+ require 'aws_ami_cleanup'
8
+
9
+ AwsAmiCleanup::Commands.start(ARGV)
@@ -0,0 +1,7 @@
1
+ require 'thor'
2
+
3
+ module AwsAmiCleanup
4
+ end
5
+
6
+ require 'aws_ami_cleanup/cleanup_amis'
7
+ require 'aws_ami_cleanup/commands'
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aws-sdk-ec2'
4
+ require 'aws-sdk-autoscaling'
5
+
6
+ module AwsAmiCleanup
7
+ class CleanupAmis
8
+ DEFAULT_NUMBER_OF_AMIS_TO_KEEP = 3
9
+
10
+ attr_accessor :region, :number_of_amis_to_keep
11
+
12
+ def initialize(region, number_of_amis_to_keep)
13
+ @region = region
14
+
15
+ @number_of_amis_to_keep = number_of_amis_to_keep || DEFAULT_NUMBER_OF_AMIS_TO_KEEP
16
+ if number_of_amis_to_keep <= 0
17
+ raise 'Number of AMIs to keep must be higher than 0.'
18
+ end
19
+ end
20
+
21
+ def execute!(ami_name:, ami_owner:)
22
+ potential_amis_to_remove = amis(ami_name, ami_owner)
23
+ ami_ids = potential_amis_to_remove.collect(&:image_id)
24
+ ami_ids_to_remove = ami_ids - amis_in_use
25
+ potential_amis_to_remove.keep_if {|a| ami_ids_to_remove.include?(a.image_id) }
26
+
27
+ if potential_amis_to_remove.count > number_of_amis_to_keep
28
+ amis_to_remove = potential_amis_to_remove[number_of_amis_to_keep..-1]
29
+ amis_to_keep = potential_amis_to_remove[0..(number_of_amis_to_keep-1)]
30
+
31
+ puts "Deregistering old AMIs..."
32
+ amis_to_remove.each do |ami|
33
+ ebs_mappings = ami.block_device_mappings
34
+ puts "Deregistering #{ami.image_id}"
35
+ ami.deregister
36
+ delete_ami_snapshots(ebs_mappings)
37
+ end
38
+
39
+ puts "Currently active AMIs..."
40
+ amis_to_keep.each do |ami|
41
+ puts ami.image_id
42
+ end
43
+ else
44
+ puts "no AMIs to clean."
45
+ end
46
+ end
47
+
48
+ protected
49
+
50
+ def ec2
51
+ @__ec2 ||= Aws::EC2::Client.new(region: region)
52
+ end
53
+
54
+ def auto_scaling
55
+ @__auto_scaling ||= Aws::AutoScaling::Client.new(region: region)
56
+ end
57
+
58
+ def amis(ami_name, ami_owner)
59
+ return @__amis unless @__amis.nil?
60
+
61
+ # Cannot lookup by Name tag because that's only available from the owner account.
62
+ describe_images_params = { owners: [ ami_owner ] }
63
+ all_images_from_owner = ec2.describe_images(describe_images_params).images
64
+ name_matching_images = all_images_from_owner.filter {|i| i.name.match?(ami_name) }
65
+
66
+ @__amis = sort_by_created_at(name_matching_images)
67
+ end
68
+
69
+ def sort_by_created_at(collection)
70
+ # Returns items oredered by creation_date from newer to older
71
+ collection.sort {|a, b| DateTime.parse(b.creation_date) <=> DateTime.parse(a.creation_date) }
72
+ end
73
+
74
+ def amis_in_use
75
+ image_ids = []
76
+
77
+ autoscaling_groups = auto_scaling.describe_auto_scaling_groups.auto_scaling_groups
78
+
79
+ # Find AMIs used by auto scaling groups with launch templates
80
+ launch_template_ids = autoscaling_groups.reject {|asg| asg.launch_template.nil? }
81
+ .collect {|asg| asg.launch_template.launch_template_id }
82
+ launch_template_ids.each do |launch_template_id|
83
+ image_ids << ec2.describe_launch_template_versions(launch_template_id: launch_template_id, max_results: 1)
84
+ .launch_template_versions
85
+ .first
86
+ .launch_template_data
87
+ .image_id
88
+ end
89
+
90
+ # Find AMIs used by auto scaling groups with launch configurations
91
+ launch_configuration_names = autoscaling_groups.filter {|asg| asg.launch_template.nil? }
92
+ .collect {|asg| asg.launch_configuration_name }
93
+ launch_configurations = auto_scaling.describe_launch_configurations(launch_configuration_names: launch_configuration_names).launch_configurations
94
+ image_ids += launch_configurations.map(&:image_id)
95
+
96
+ # Finally, find AMIs used by instances not belonging to auto scaling groups
97
+ ec2_reservations = ec2.describe_instances
98
+ image_ids += ec2_reservations.reservations.collect {|res| res.instances.map(&:image_id) }.flatten
99
+
100
+ image_ids.flatten
101
+ end
102
+
103
+ def delete_ami_snapshots(ebs_mappings)
104
+ ebs_mappings.each do |ebs_mapping|
105
+ # Skip ephimeral block devices
106
+ next if ebs_mapping.ebs.nil? || ebs_mapping.ebs.snapshot_id.nil?
107
+
108
+ snapshot_id = ebs_mapping.ebs.snapshot_id
109
+ puts "Deleting snapshot #{snapshot_id}"
110
+ snapshot = Aws::EC2::Snapshot.new(snapshot_id)
111
+ snapshot.delete
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AwsAmiCleanup
4
+ class Commands < Thor
5
+ desc "clean_amis", "delete unused AMIs owned by ami_owner with ami_name name"
6
+ option :ami_name, required: true
7
+ option :ami_owner, required: true
8
+ option :number_of_amis_to_keep, required: false
9
+ option :region, required: false
10
+ def clean_amis
11
+ cleanup_amis = AwsAmiCleanup::CleanupAmis.new(region, options[:number_of_amis_to_keep]&.to_i)
12
+
13
+ cleanup_amis.execute!(ami_name: options[:ami_name], ami_owner: options[:ami_owner])
14
+ end
15
+
16
+ desc "console", "interactive session"
17
+ def console
18
+ require 'byebug'
19
+ byebug
20
+ end
21
+
22
+ protected
23
+
24
+ def region
25
+ options[:region] || 'us-east-1'
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AwsAmiCleanup
4
+ VERSION = "0.1"
5
+ end
6
+
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aws_ami_cleanup
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - Diego Marcet
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-02-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk-ec2
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: aws-sdk-autoscaling
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: thor
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: byebug
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '11'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '11'
69
+ description:
70
+ email: diego@controlshiftlabs.com
71
+ executables:
72
+ - cleanup_amis
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - ".ruby-gemset"
78
+ - ".ruby-version"
79
+ - Gemfile
80
+ - README.md
81
+ - aws_ami_cleanup.gemspec
82
+ - bin/cleanup_amis
83
+ - lib/aws_ami_cleanup.rb
84
+ - lib/aws_ami_cleanup/cleanup_amis.rb
85
+ - lib/aws_ami_cleanup/commands.rb
86
+ - lib/aws_ami_cleanup/version.rb
87
+ homepage: http://github.com/controlshift/aws_ami_cleanup
88
+ licenses:
89
+ - MIT
90
+ metadata: {}
91
+ post_install_message:
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubygems_version: 3.0.8
107
+ signing_key:
108
+ specification_version: 4
109
+ summary: Script for deleting obsolete AMIs
110
+ test_files: []