forked from oniksfly/registry_cleaner
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathregistry_cleaner.rb
executable file
·151 lines (125 loc) · 5.25 KB
/
registry_cleaner.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#!/usr/bin/env ruby
require 'net/http'
require 'openssl'
require 'json'
require 'optparse'
DEFAULT_PORT = 5000
LEAVE_TAGS = 5
options = {}
OptionParser.new do |opts|
opts.banner = "Usage: ./registry_cleaner.rb [options]"
opts.on('-r', '--repository NAME', 'Registry repository name') { |v| options[:repository] = v }
opts.on('-t', '--tags_count COUNT', 'How many tags to leave') { |v| options[:leave_tags] = v.to_i }
opts.on('-h', '--host NAME', 'Registry host name, required') { |v| options[:host] = v }
opts.on('-p', '--port PORT', 'Registry port') { |v| options[:port] = v }
opts.on('-al', '--auth_login LOGIN', 'Basic auth login') { |v| options[:basic_auth_login] = v }
opts.on('-ap', '--auth_password PASSWORD', 'Basic auth login') { |v| options[:basic_auth_password] = v }
end.parse!
raise OptionParser::MissingArgument.new('host') if options[:host].nil?
options[:port] = DEFAULT_PORT if options[:port].nil?
options[:leave_tags] = LEAVE_TAGS if options[:leave_tags].nil?
uri = URI("#{options[:host]}:#{options[:port]}")
def network_request(local_uri, method: :get, headers: {}, options: {})
Net::HTTP.start(local_uri.host, local_uri.port, use_ssl: local_uri.scheme == 'https', verify_mode: OpenSSL::SSL::VERIFY_NONE) do |http|
method_class = case method
when :get
Net::HTTP::Get
when :delete
Net::HTTP::Delete
else
raise ArgumentError.new('Unspopported request method')
end
request = method_class.send('new', local_uri.request_uri)
headers.each{ |name, value| request[name] = value } if headers.any?
unless options[:basic_auth_login].nil? && options[:basic_auth_password].nil?
request.basic_auth options[:basic_auth_login], options[:basic_auth_password]
end
return http.request request
end
end
def network_get_request(local_uri, options, headers: {})
network_request(local_uri, options: options, headers: headers)
end
def network_delete_request(local_uri, options, headers: {})
network_request(local_uri, method: :delete, options: options, headers: headers)
end
def get_manifest(uri, repository, tag, options)
uri.path = "/v2/#{repository}/manifests/#{tag}"
network_get_request(uri, options, headers: { 'Accept': 'application/vnd.docker.distribution.manifest.v2+json' })
end
def remove_manifest(uri, repository, manifest)
uri.path = "/v2/#{repository}/manifests/#{manifest}"
network_delete_request(uri, headers: { 'Accept': 'application/vnd.docker.distribution.manifest.v2+json' })
end
def registry_response_tags(response)
result = {}
unless response.nil?
begin
json = JSON.parse(response)
if !json['tags'].nil? && json['tags'].any?
json['tags'].each do |tag|
category = tag.gsub(/[\d]/, '')
result[category] = [] if result[category].nil?
result[category].push(tag)
end
result.keys.each{ |category_name| result[category_name].sort_by!{ |t| t.gsub(/[^\d]/, '').to_i } }
end
rescue JSON::ParserError => e
puts "Can't parse JSON: #{e}"
end
end
result
end
def process_repository(uri, repository, leave_tags, options)
uri.path = "/v2/#{repository}/tags/list"
response = network_get_request(uri, options)
if response.code.to_i == 200
tags_categories = registry_response_tags(response.body)
if tags_categories.count > 0
tags_categories.each do |category, tags|
if tags.count > leave_tags
tags_to_delete = tags[0...-leave_tags]
puts "Tags to delete in category `#{category}`: #{tags_to_delete.join(', ')}"
tags_to_delete.each do |tag|
manifest_response = get_manifest(uri, repository, tag, options)
manifest = manifest_response['Docker-Content-Digest']
unless manifest.nil?
puts "\t#{repository}:#{tag} removing manifest #{manifest}"
remove_manifest(uri, repository, manifest)
end
end
else
"Not enough versions for #{repository}"
end
end
else
puts "No tags categories for #{repository}"
end
else
puts "Wrong response with http code #{response.code}"
end
puts 'Complete'
end
def list_repositories(uri, options)
uri.path = "/v2/_catalog"
response = network_get_request(uri, options, headers: { 'Accept': 'application/vnd.docker.distribution.manifest.v2+json' })
if response.nil?
puts "Wrong result while listing repositories"
else
begin
json = JSON.parse(response.body)
if json['repositories'].any?
json['repositories'].each{ |repo| puts "\t#{repo}" }
else
puts "No repositories available"
end
rescue JSON::ParserError => e
puts "Can't parse JSON: #{e}"
end
end
end
if options[:repository].nil?
list_repositories(uri, options)
else
process_repository(uri, options[:repository], options[:leave_tags], options)
end