awsclean 1.0
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 +7 -0
- data/.gitignore +31 -0
- data/.hound.yml +3 -0
- data/.rubocop.yml +1076 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +73 -0
- data/README.md +100 -0
- data/Rakefile +6 -0
- data/awsclean.gemspec +42 -0
- data/bin/awsclean +27 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/awsclean.rb +16 -0
- data/lib/awsclean/ami_clean.rb +79 -0
- data/lib/awsclean/aws_command.rb +30 -0
- data/lib/awsclean/aws_extensions.rb +66 -0
- data/lib/awsclean/commands.rb +41 -0
- data/lib/awsclean/ecr_clean.rb +104 -0
- data/lib/awsclean/version.rb +7 -0
- metadata +190 -0
@@ -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
|
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: []
|