awsclean 1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,30 @@
1
+ # (c) 2017 Ribose Inc.
2
+ #
3
+
4
+ module Awsclean
5
+ class AwsCommand
6
+
7
+ class << self
8
+
9
+ def supported_regions
10
+ Aws.partition('aws').regions
11
+ .select { |region| region.services.include?(self.const_get(:SERVICE_IDENTIFIER)) }
12
+ .map(&:name)
13
+ end
14
+
15
+ def filter_regions(regions)
16
+ supported = supported_regions
17
+
18
+ if regions.first == "all"
19
+ supported_regions
20
+ else
21
+ # TODO: throw something if region not available
22
+ regions.select { |r| supported.include? r }
23
+ end
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+ end
30
+
@@ -0,0 +1,66 @@
1
+ require 'aws-sdk'
2
+
3
+ module Aws
4
+ module EC2
5
+ module Types
6
+ class Image
7
+
8
+ attr_accessor :in_use, :elegible_for_cleanup
9
+
10
+ def days_since_creation
11
+ (DateTime.now - DateTime.parse(creation_date)).to_i
12
+ end
13
+
14
+ def stale?(max_age)
15
+ days_since_creation >= max_age
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ module ECR
22
+ module Types
23
+ class ImageDetail
24
+
25
+ attr_accessor :region, :in_use, :elegible_for_cleanup
26
+
27
+ def days_since_creation
28
+ (DateTime.now - image_pushed_at.to_datetime).to_i
29
+ end
30
+
31
+ def stale?(max_age)
32
+ days_since_creation >= max_age
33
+ end
34
+
35
+ def image_uris
36
+ (image_tags || []).map { |tag| repository_uri << ':' << tag }
37
+ end
38
+
39
+ def repository_uri
40
+ sprintf('%i.dkr.ecr.%s.amazonaws.com/%s',
41
+ registry_id, region, repository_name,
42
+ )
43
+ end
44
+ end
45
+ end
46
+
47
+ class Client
48
+
49
+ def describe_all_repositories
50
+ repositories = []
51
+ next_token = nil
52
+
53
+ loop do
54
+ res = describe_repositories(next_token: next_token)
55
+ repositories << res.repositories
56
+ next_token = res.next_token
57
+ break unless next_token
58
+ end
59
+
60
+ Types::DescribeRepositoriesResponse.new(
61
+ repositories: repositories.flatten
62
+ )
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,41 @@
1
+ # (c) 2017 Ribose Inc.
2
+ #
3
+
4
+ module Awsclean
5
+ class Commands < Thor
6
+
7
+ =begin
8
+ -- -l only list out "unused" images with name, AMI ID, size, region, date created (and how many days it has been) and who created
9
+ -- -c to clean up all "unused" containers
10
+ -- -e [days] the number of days considered "unused", default is 60
11
+ -- -l and -c can be used with -e but -l and -c cannot be used together. Default mode is -l.
12
+ -- -r [regions] to specify regions to check, in the form of "us-west-1,us-east-1,..." separated by comma
13
+ =end
14
+
15
+ option :l, type: :boolean, default: true #list
16
+ option :c, type: :boolean
17
+ option :e, type: :numeric, default: 60
18
+ option :r, type: :array, default: %w(all)
19
+
20
+ # TODO: dynamically generate these based on listing out subclasses of AwsCommand?
21
+
22
+ desc 'clean_amis', "Cleanup unused AMI's"
23
+ def clean_amis
24
+ puts "[clean_amis] Running AmiClean"
25
+ AmiClean.run options
26
+ end
27
+
28
+ option :l, type: :boolean, default: true #list
29
+ option :c, type: :boolean
30
+ option :e, type: :numeric, default: 60
31
+ option :r, type: :array, default: %w(all)
32
+
33
+ desc 'clean_ecr_images', "Cleanup unused ECR container images"
34
+ def clean_ecr_images
35
+ puts "[clean_ecr_images] Running EcrClean"
36
+ EcrClean.run options
37
+ end
38
+
39
+ end
40
+ end
41
+
@@ -0,0 +1,104 @@
1
+ # (c) 2017 Ribose Inc.
2
+ #
3
+
4
+ module Awsclean
5
+ class EcrClean < AwsCommand
6
+
7
+ SERVICE_IDENTIFIER = "ECR"
8
+
9
+ IMAGE_LIST_HEADER = [
10
+ 'REGION', 'IN USE?', 'REPOSITORY URI', 'TAGS',
11
+ 'IMAGE ID', 'CREATED', 'SIZE', 'ELEGIBLE FOR CLEANUP?'
12
+ ]
13
+ IMAGE_LIST_FORMAT = '%-16s%-12s%-64s%-24s%-24s%-42s%-12s%-24s'
14
+
15
+ def self.run options
16
+ regions = filter_regions(options[:r])
17
+
18
+ if regions.empty?
19
+ puts "Please specify region with -r [regions, e.g., 'all' or 'us-west-1 us-east-1']"
20
+ exit 1
21
+ end
22
+
23
+ # List images used in active task definitions from all regions.
24
+ # This is a global operation so we run it once.
25
+ #
26
+ images_in_active_task_defs = supported_regions.flat_map do |region|
27
+ ecs = Aws::ECS::Client.new(region: region)
28
+ res = ecs.list_task_definitions(status: 'ACTIVE', max_results: 100)
29
+
30
+ res.task_definition_arns.flat_map do |td|
31
+ ecs.describe_task_definition(task_definition: td)
32
+ .task_definition.container_definitions.map(&:image)
33
+ end
34
+ end
35
+
36
+ regions.each do |region|
37
+ puts "[clean_ecr_images] Checking region: #{region}"
38
+
39
+ ecr = Aws::ECR::Client.new(region: region)
40
+
41
+ # List all image repositories
42
+ #
43
+ res = ecr.describe_all_repositories
44
+ images = res.repositories.flat_map do |repo|
45
+ ecr.describe_images(repository_name: repo.repository_name)
46
+ .image_details
47
+ end
48
+
49
+ images.each do |image|
50
+ image.region = region
51
+ image.in_use = !(images_in_active_task_defs & image.image_uris).empty?
52
+ image.elegible_for_cleanup = (image.stale?(options[:e]) && !image.in_use)
53
+ end
54
+
55
+ # We always show a list of images
56
+ #
57
+ print_image_list_header
58
+ images.each { |image| print_image_list_entry(image) }
59
+
60
+ if options[:c]
61
+ images_to_delete =
62
+ images.select(&:elegible_for_cleanup).group_by(&:repository_name)
63
+
64
+ images_to_delete.each do |repository_name, pack|
65
+ ecr.batch_delete_image(
66
+ repository_name: repository_name,
67
+ image_ids: pack.map { |i| { image_digest: i.image_digest } }
68
+ )
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def self.print_image_list_header
77
+ puts sprintf(IMAGE_LIST_FORMAT, *IMAGE_LIST_HEADER)
78
+ end
79
+
80
+ def self.print_image_list_entry(image)
81
+ created = image.image_pushed_at.iso8601
82
+ created << " (#{image.days_since_creation} days ago)"
83
+
84
+ size = (image.image_size_in_bytes / 1024 / 1024).round(2).to_s
85
+ size << ' MB'
86
+
87
+ unless image.image_tags
88
+ puts sprintf(IMAGE_LIST_FORMAT,
89
+ image.region, image.in_use, image.repository_uri,
90
+ "(!TAG)", image.image_digest[0,19], created,
91
+ size, image.elegible_for_cleanup
92
+ )
93
+ else
94
+ image.image_tags.each do |tag|
95
+ puts sprintf(IMAGE_LIST_FORMAT,
96
+ image.region, image.in_use, image.repository_uri,
97
+ tag, image.image_digest[0,19], created,
98
+ size, image.elegible_for_cleanup
99
+ )
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,7 @@
1
+ # (c) 2017 Ribose Inc.
2
+ #
3
+
4
+ module Awsclean
5
+ VERSION = "1.0"
6
+ end
7
+
metadata ADDED
@@ -0,0 +1,190 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: awsclean
3
+ version: !ruby/object:Gem::Version
4
+ version: '1.0'
5
+ platform: ruby
6
+ authors:
7
+ - Ribose Inc.
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-08-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.14'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.14'
27
+ - !ruby/object:Gem::Dependency
28
+ name: thor
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.19.4
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.19.4
41
+ - !ruby/object:Gem::Dependency
42
+ name: aws-sdk
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 2.7.4
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 2.7.4
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: bundler
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.14'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.14'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '10.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '10.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '3.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '3.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rspec-expectations
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '3.0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '3.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: codecov
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 0.1.10
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 0.1.10
139
+ description: CLI to clean up AWS AMIs and ECR images
140
+ email:
141
+ - open.source@ribose.com
142
+ executables:
143
+ - awsclean
144
+ extensions: []
145
+ extra_rdoc_files: []
146
+ files:
147
+ - ".gitignore"
148
+ - ".hound.yml"
149
+ - ".rubocop.yml"
150
+ - ".travis.yml"
151
+ - CODE_OF_CONDUCT.md
152
+ - Gemfile
153
+ - Gemfile.lock
154
+ - README.md
155
+ - Rakefile
156
+ - awsclean.gemspec
157
+ - bin/awsclean
158
+ - bin/console
159
+ - bin/setup
160
+ - lib/awsclean.rb
161
+ - lib/awsclean/ami_clean.rb
162
+ - lib/awsclean/aws_command.rb
163
+ - lib/awsclean/aws_extensions.rb
164
+ - lib/awsclean/commands.rb
165
+ - lib/awsclean/ecr_clean.rb
166
+ - lib/awsclean/version.rb
167
+ homepage: https://www.ribose.com
168
+ licenses: []
169
+ metadata: {}
170
+ post_install_message:
171
+ rdoc_options: []
172
+ require_paths:
173
+ - lib
174
+ required_ruby_version: !ruby/object:Gem::Requirement
175
+ requirements:
176
+ - - ">="
177
+ - !ruby/object:Gem::Version
178
+ version: '0'
179
+ required_rubygems_version: !ruby/object:Gem::Requirement
180
+ requirements:
181
+ - - ">="
182
+ - !ruby/object:Gem::Version
183
+ version: '0'
184
+ requirements: []
185
+ rubyforge_project:
186
+ rubygems_version: 2.5.2
187
+ signing_key:
188
+ specification_version: 4
189
+ summary: CLI to clean up AWS AMIs and ECR images
190
+ test_files: []