docker-cleaner 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 03ac980101dedfb38b309cc3464a839141a54361
4
+ data.tar.gz: 8accceff7c76e77e357d1cfd81bd0e6ded224789
5
+ SHA512:
6
+ metadata.gz: 15d8c9d9469c5a62ad7931f1a9665270ac607d50b1223c33da84954f0e1674842391010b5b8442dd3afab0e3a5ef1288f0676a85b6ba9481f86af69668fabdd1
7
+ data.tar.gz: ecaa894f663a25bee97164aaff6b1f6b49edfcc3c372b7cc37146362b6ef44cb455a5bce50de280bcff0c1a99abc1813bd7675310e69d49f8e0d0eb2ae373f2e
@@ -0,0 +1,2 @@
1
+ /.bundle
2
+ /binstubs
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'docker-api', '~> 1.18'
4
+ gem 'docker-cleaner', path: "."
@@ -0,0 +1,24 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ docker-cleaner (0.1)
5
+ docker-api (~> 1.17)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ docker-api (1.21.4)
11
+ excon (>= 0.38.0)
12
+ json
13
+ excon (0.45.3)
14
+ json (1.8.2)
15
+
16
+ PLATFORMS
17
+ ruby
18
+
19
+ DEPENDENCIES
20
+ docker-api (~> 1.18)
21
+ docker-cleaner!
22
+
23
+ BUNDLED WITH
24
+ 1.12.5
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ path = File.expand_path(File.join(File.dirname(__FILE__), 'lib'))
4
+ $LOAD_PATH << path
5
+
6
+ require 'rubygems'
7
+ require 'bundler/setup'
8
+ Bundler.require :default
9
+ require 'docker'
10
+
11
+ require 'optparse'
12
+ require 'logger'
13
+
14
+ STOP_DOCKER_CLEANER_FILE = '/tmp/stop-docker-cleaner'.freeze
15
+
16
+ options = {}
17
+ options[:registry] = ENV["REGISTRY"]
18
+ options[:prefix] = ENV["PREFIX"]
19
+ options[:log] = ENV["LOG_FILE"]
20
+ options[:docker] = ENV["DOCKER_HOST"]
21
+ options[:delete_delay] = ENV["DELETE_DELAY"]
22
+ options[:registries] = ENV["REGISTRIES"] || options[:registry]
23
+ options[:retention] = ENV["RETENTION"]
24
+
25
+ OptionParser.new do |opts|
26
+ opts.banner = "Usage: docker_clean [options]"
27
+
28
+ opts.on("--delete-delay=DELAY", "Delay in seconds between container/image deletion") do |r|
29
+ options[:delete_delay] = r
30
+ end
31
+ opts.on("-pPREFIX", "--prefix=PREFIX", "Prefix of images your want to clean") do |r|
32
+ options[:prefix] = r
33
+ end
34
+ opts.on("-rREGISTRY", "--registry=REGISTRY", "Registry") do |r|
35
+ options[:registries] = r
36
+ end
37
+ opts.on("--registries=REGISTRIES", "Registries") do |r|
38
+ options[:registries] = r
39
+ end
40
+ opts.on("-lLOG", "--log=LOG", "Log file") do |r|
41
+ options[:log] = r
42
+ end
43
+ opts.on("-dDOCKER", "--docker=DOCKER", "Docker endpoint") do |r|
44
+ options[:docker] = r
45
+ end
46
+ opts.on("--retention=RETENTION", "How long images should be kept before deletion (in hours)") do |r|
47
+ options[:retention] = r
48
+ end
49
+
50
+ opts.on("--force", "Force the clean even if the #{STOP_DOCKER_CLEANER_FILE} file is present") do |r|
51
+ options[:force] = true
52
+ end
53
+ end.parse!
54
+
55
+ logger = Logger.new options[:log] || $stdout
56
+
57
+ if options[:registries].nil?
58
+ $stderr.puts "--registries option should be filled"
59
+ exit -1
60
+ end
61
+
62
+ options[:registries] = options[:registries].split(",").map(&:chomp)
63
+
64
+ options[:retention] = options[:retention].to_i
65
+ options[:retention] = 6 if options[:retention] == 0
66
+
67
+ Docker.url = options[:docker] || "http://localhost:4243"
68
+ Docker.options = { read_timeout: 300, write_timeout: 300 }
69
+
70
+ if File.file? STOP_DOCKER_CLEANER_FILE
71
+ logger.info 'Stop docker cleaner file is present'
72
+
73
+ if !options[:force]
74
+ logger.info 'Aborting'
75
+ exit 0
76
+ else
77
+ logger.info 'Force flag is present, continuing...'
78
+ end
79
+ end
80
+
81
+ require 'docker_cleaner'
82
+
83
+ DockerCleaner.run(
84
+ options[:registries], options[:prefix], logger,
85
+ delay: options[:delete_delay].to_i,
86
+ retention: options[:retention],
87
+ )
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/docker_cleaner/version', __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "docker-cleaner"
6
+ s.version = DockerCleaner::VERSION
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = ["Leo Unbekandt"]
9
+ s.email = ["leo@scalingo.com"]
10
+ s.homepage = "https://github.com/Scalingo/docker-cleaner"
11
+ s.summary = "Small utility to clean old containers and images"
12
+ s.description = "Small utility to clean old docker data, containers according to some settings"
13
+ s.license = "MIT"
14
+
15
+ s.add_dependency "docker-api", "~> 1.0"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
19
+ s.require_path = 'lib'
20
+ end
21
+
@@ -0,0 +1,9 @@
1
+ require 'docker_cleaner/containers'
2
+ require 'docker_cleaner/images'
3
+
4
+ module DockerCleaner
5
+ def self.run(registries, prefix, logger, opts)
6
+ DockerCleaner::Containers.new(logger, opts).run
7
+ DockerCleaner::Images.new(registries, prefix, logger, opts).run
8
+ end
9
+ end
@@ -0,0 +1,46 @@
1
+ module DockerCleaner
2
+ class Containers
3
+ def initialize(logger, opts = {})
4
+ @logger = logger
5
+ @delay = opts.fetch(:delay, 0)
6
+ end
7
+
8
+ def remove(container)
9
+ @logger.info "Remove #{container.id[0...10]} - #{container.info["Image"]} - #{container.info["Names"][0]}"
10
+ container.remove v: true
11
+ @logger.info "Remove #{container.id[0...10]} - #{container.info["Image"]} - #{container.info["Names"][0]}... OK"
12
+ end
13
+
14
+ def run
15
+ # Remove stopped container which stopped with code '0'
16
+ two_hours_ago = Time.now.to_i - 2 * 3600
17
+ Docker::Container.all(all: true).select{ |container|
18
+ status = container.info["Status"]
19
+ (status == "Created" && container.info["Created"].to_i < two_hours_ago) ||
20
+ (status.include?("Exited (") && container.info["Created"].to_i < two_hours_ago)
21
+ }.each do |container|
22
+ remove(container)
23
+ sleep(@delay)
24
+ end
25
+
26
+ containers_per_app = {}
27
+ Docker::Container.all(all: true).select{ |container|
28
+ container.info["Status"].include?("Exited")
29
+ }.each{ |container|
30
+ app = container.info["Image"].split(":", 2)[0]
31
+ if containers_per_app[app].nil?
32
+ containers_per_app[app] = [container]
33
+ else
34
+ containers_per_app[app] << container
35
+ end
36
+ }
37
+ containers_per_app.each do |app, containers|
38
+ containers.shift
39
+ containers.each do |container|
40
+ remove(container)
41
+ sleep(@delay)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,117 @@
1
+ module DockerCleaner
2
+ class Images
3
+ def initialize registries, prefix, logger, opts = {}
4
+ @prefix = prefix || ""
5
+ @registries = registries
6
+ @logger = logger
7
+ @delay = opts.fetch(:delay, 0)
8
+ @retention = Time.now.to_i - opts.fetch(:retention, 6) * 3600
9
+ end
10
+
11
+ def run
12
+ clean_old_images
13
+ clean_unnamed_images
14
+ clean_unused_images
15
+ end
16
+
17
+ def clean_unnamed_images
18
+ Docker::Image.all.select do |image|
19
+ image.info["RepoTags"].nil? || image.info["RepoTags"][0] == "<none>:<none>"
20
+ end.each do |image|
21
+ @logger.info "Remove unnamed image #{image.id[0...10]}"
22
+ begin
23
+ image.remove
24
+ sleep(@delay)
25
+ rescue Docker::Error::NotFoundError
26
+ rescue Docker::Error::ConflictError => e
27
+ @logger.warn "Conflict when removing #{image.id[0...10]}"
28
+ @logger.warn " ! #{e.message}"
29
+ end
30
+ end
31
+ end
32
+
33
+ def clean_old_images
34
+ apps = images_with_latest
35
+ apps.each do |app, images|
36
+ if app =~ /.*-tmax$/
37
+ next
38
+ end
39
+ images.each do |i|
40
+ unless i.info["Created"] == apps["#{app}-tmax"]
41
+ @logger.info "Remove #{i.info['RepoTags'][0]} => #{i.id[0...10]}"
42
+ begin
43
+ i.remove
44
+ sleep(@delay)
45
+ rescue Docker::Error::NotFoundError
46
+ rescue Docker::Error::ConflictError => e
47
+ @logger.warn "Conflict when removing #{i.info['RepoTags'][0]} - ID: #{i.id[0...10]}"
48
+ @logger.warn " ! #{e.message}"
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ def images_with_latest
56
+ images ||= Docker::Image.all
57
+ apps = {}
58
+
59
+ images.each do |i|
60
+ # RepoTags can be nil sometimes, in this case we ignore the image
61
+ next if i.info["RepoTags"].nil?
62
+ if registries_include?(i.info["RepoTags"][0])
63
+ name = i.info["RepoTags"][0].split(":")[0]
64
+ tmax = "#{name}-tmax"
65
+
66
+ if apps[name].nil?
67
+ apps[name] = [i]
68
+ else
69
+ apps[name] << i
70
+ end
71
+
72
+ if apps[tmax].nil?
73
+ apps[tmax] = i.info["Created"]
74
+ elsif apps[tmax] < i.info["Created"]
75
+ apps[tmax] = i.info["Created"]
76
+ end
77
+ end
78
+ end
79
+ apps
80
+ end
81
+
82
+ def clean_unused_images
83
+ used_images = Docker::Container.all.map{|c| c.info["Image"]}.select{|i| registries_include?(i) }.uniq
84
+ # Images older than 2 months
85
+ images = Docker::Image.all.select{|i| i.info["RepoTags"] && registries_include?(i.info["RepoTags"][0]) && i.info["Created"] < @retention }
86
+ image_repos = images.map{|i| i.info["RepoTags"][0]}
87
+ unused_images = image_repos - used_images
88
+
89
+ unused_images.each do |i|
90
+ image = images.select{|docker_image| docker_image.info["RepoTags"][0] == i}[0]
91
+ @logger.info "Remove unused image #{image.info['RepoTags'][0]} => #{image.id[0...10]}"
92
+ begin
93
+ image.remove
94
+ sleep(@delay)
95
+ rescue Docker::Error::NotFoundError
96
+ rescue Docker::Error::ConflictError => e
97
+ @logger.warn "Conflict when removing #{image.info['RepoTags'][0]} - ID: #{image.id[0...10]}"
98
+ @logger.warn " ! #{e.message}"
99
+ end
100
+ end
101
+ end
102
+
103
+ protected
104
+
105
+ def registries_include?(image)
106
+ if image.nil? || image == ''
107
+ return false
108
+ end
109
+ @registries.each do |registry|
110
+ if image =~ /^#{registry}\/#{@prefix}/
111
+ return true
112
+ end
113
+ end
114
+ return false
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,3 @@
1
+ module DockerCleaner
2
+ VERSION = "0.3.0"
3
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: docker-cleaner
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Leo Unbekandt
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-12-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: docker-api
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ description: Small utility to clean old docker data, containers according to some
28
+ settings
29
+ email:
30
+ - leo@scalingo.com
31
+ executables:
32
+ - docker-cleaner
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - ".gitignore"
37
+ - Gemfile
38
+ - Gemfile.lock
39
+ - bin/docker-cleaner
40
+ - docker-cleaner.gemspec
41
+ - lib/docker_cleaner.rb
42
+ - lib/docker_cleaner/containers.rb
43
+ - lib/docker_cleaner/images.rb
44
+ - lib/docker_cleaner/version.rb
45
+ homepage: https://github.com/Scalingo/docker-cleaner
46
+ licenses:
47
+ - MIT
48
+ metadata: {}
49
+ post_install_message:
50
+ rdoc_options: []
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ requirements: []
64
+ rubyforge_project:
65
+ rubygems_version: 2.6.11
66
+ signing_key:
67
+ specification_version: 4
68
+ summary: Small utility to clean old containers and images
69
+ test_files: []