docker-janitor 0.0.3
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/bin/docker-janitor +238 -0
- data/lib/docker_janitor.rb +3 -0
- data/lib/docker_janitor/config.rb +52 -0
- data/lib/docker_janitor/shell.rb +25 -0
- data/lib/docker_janitor/version.rb +3 -0
- metadata +134 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 660f8254eeffba306d7a7b33be76730913a3436d
|
4
|
+
data.tar.gz: a18ced013b698ad73798e4eb4d40cc63508331e3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2fc2864783ba673f7ed5eea77a30f997842f3c66e0573f81a76d9a551b0cc9a8a7334fb2ae6bc6216fc3572224d55e85a4485d254d5817f875af24165438e5c0
|
7
|
+
data.tar.gz: f2e2ba806e48f9ca7affb0fa63e584eb6f1ac5d27b1d96c06948948ae3adbd994ea5b97059c3cdba9aa3b3b5e1ad4e94f7ac12118e98e648ca59a9fea1b7e1b6
|
data/bin/docker-janitor
ADDED
@@ -0,0 +1,238 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'thor'
|
4
|
+
require 'sys/filesystem'
|
5
|
+
require 'ostruct'
|
6
|
+
require 'colorize'
|
7
|
+
|
8
|
+
require 'docker_janitor'
|
9
|
+
|
10
|
+
class DockerJanitorBin < Thor
|
11
|
+
class_option :dryrun, type: :boolean, aliases: ['dry-run', 'd'], default: false
|
12
|
+
|
13
|
+
desc 'clean', 'Clean up leftover docker cruft (does containers, images, volumes)'
|
14
|
+
long_desc <<-LONGDESC
|
15
|
+
Clean up leftover docker cruft
|
16
|
+
|
17
|
+
This command will clean up the saved cache items that docker
|
18
|
+
fills up your disk with. This includes containers that have
|
19
|
+
exited, images that are unlabeled, and old volumes.
|
20
|
+
|
21
|
+
To restrict what is cleaned up, you can specify [-c|--containers],
|
22
|
+
[-i|--images], and/or [-v|--volumes]. If nothing is specified,
|
23
|
+
all three will be cleaned.
|
24
|
+
|
25
|
+
Named volumes are not included by default to avoid data loss.
|
26
|
+
If you want to include them, pass --include-named or -n or --named
|
27
|
+
|
28
|
+
> $ docker-janitor [-c|--containers] [-i|--images] [-v|--volumes]
|
29
|
+
LONGDESC
|
30
|
+
option :containers, type: :boolean, aliases: ['c', 'container'], default: false
|
31
|
+
option :images, type: :boolean, aliases: ['i', 'image'], default: false
|
32
|
+
option :volumes, type: :boolean, aliases: ['v', 'volumes'], default: false
|
33
|
+
option :'include-named', type: :boolean, aliases: ['n', 'named'], default: false
|
34
|
+
def clean
|
35
|
+
args = options.dup
|
36
|
+
if !args[:containers] && !args[:images] && !args[:volumes]
|
37
|
+
args[:containers] = true
|
38
|
+
args[:images] = true
|
39
|
+
args[:volumes] = true
|
40
|
+
end
|
41
|
+
exec_clean(args)
|
42
|
+
end
|
43
|
+
|
44
|
+
desc 'containers', 'Delete exited containers'
|
45
|
+
def containers
|
46
|
+
exec_clean({
|
47
|
+
dryrun: options[:dryrun],
|
48
|
+
containers: true,
|
49
|
+
images: false,
|
50
|
+
volumes: false
|
51
|
+
})
|
52
|
+
end
|
53
|
+
|
54
|
+
desc 'images', 'Delete unlabeled imaged'
|
55
|
+
def images
|
56
|
+
exec_clean({
|
57
|
+
dryrun: options[:dryrun],
|
58
|
+
containers: false,
|
59
|
+
images: true,
|
60
|
+
volumes: false
|
61
|
+
})
|
62
|
+
end
|
63
|
+
|
64
|
+
desc 'volumes', 'Delete orphaned volumes'
|
65
|
+
def volumes
|
66
|
+
exec_clean({
|
67
|
+
dryrun: options[:dryrun],
|
68
|
+
containers: false,
|
69
|
+
images: false,
|
70
|
+
volumes: true
|
71
|
+
})
|
72
|
+
end
|
73
|
+
|
74
|
+
desc 'config-file', 'Write a default config file'
|
75
|
+
long_desc <<-LONGDESC
|
76
|
+
Writes a dory config file to #{DockerJanitor::Config.filename}
|
77
|
+
containing the default settings. This can then be configured
|
78
|
+
as preferred.
|
79
|
+
LONGDESC
|
80
|
+
def config_file
|
81
|
+
exec_config_file(options)
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def exec_clean(options)
|
87
|
+
mounts = Sys::Filesystem.mounts.select do |m|
|
88
|
+
%w[ntfs vfat xfs].include?(m) ||
|
89
|
+
m.mount_type =~ /^ext[234]$/i ||
|
90
|
+
m.mount_type =~ /^reiser/i
|
91
|
+
end
|
92
|
+
mounts.map! do |m|
|
93
|
+
OpenStruct.new({
|
94
|
+
mount: m,
|
95
|
+
mbfree: mb_free(m.mount_point)
|
96
|
+
})
|
97
|
+
end
|
98
|
+
|
99
|
+
if options[:containers]
|
100
|
+
puts "[*] Deleting stopped containers".cyan
|
101
|
+
containers = DockerJanitor::Sh.run_command(
|
102
|
+
"docker ps -a | grep -E 'Exited|Created'"
|
103
|
+
).stdout.split("\n")
|
104
|
+
puts "[*] #{containers.count} containers to delete".green
|
105
|
+
containers.each do |cont|
|
106
|
+
id_sha, image, command, created, status, ports, name = cont.split(/\s{2,}/)
|
107
|
+
unless name
|
108
|
+
name = ports
|
109
|
+
ports = ''
|
110
|
+
end
|
111
|
+
if options[:dryrun]
|
112
|
+
puts "[*] Not actually deleting containers (dry run)".yellow
|
113
|
+
else
|
114
|
+
if DockerJanitor::Config.settings[:"docker-janitor"][:safe_containers].any?{ |safe_container|
|
115
|
+
name =~ /#{safe_container}/ ||
|
116
|
+
id_sha =~ /#{safe_container}/ ||
|
117
|
+
image =~ /#{safe_container}/
|
118
|
+
}
|
119
|
+
puts "[*] Container '#{name}' with ID '#{id_sha}' is protected. " \
|
120
|
+
"Not deleting.".yellow
|
121
|
+
else
|
122
|
+
puts "[*] Deleting container '#{name}' with ID '#{id_sha}' created #{created}...".blue
|
123
|
+
unless DockerJanitor::Sh.run_command("docker rm #{id_sha}").success?
|
124
|
+
puts "[*] Failed deleting container '#{name}' with ID " \
|
125
|
+
"'#{id_sha}' with command '#{command}' created " \
|
126
|
+
"#{created}. Status: '#{status}'".red
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
if options[:images]
|
134
|
+
puts "[*] Deleting all unlabeled images".cyan
|
135
|
+
to_del = DockerJanitor::Sh.run_command(
|
136
|
+
"docker images | grep -iE '^<none>' | awk '{print $3}'"
|
137
|
+
).stdout.split("\n")
|
138
|
+
puts "[*] #{to_del.count} images to delete".green
|
139
|
+
puts "[*] #{to_del.join(' ')}".blue
|
140
|
+
if to_del.empty?
|
141
|
+
puts "[*] No unlabeled images to delete!".green
|
142
|
+
else
|
143
|
+
if options[:dryrun]
|
144
|
+
puts "[*] Not actually deleting images (dry run)".yellow
|
145
|
+
else
|
146
|
+
if del_images.success?
|
147
|
+
puts "[*] Unlabeled images successfully delete".green
|
148
|
+
else
|
149
|
+
puts "[*] Error deleting unlabeled images".red
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
if options[:volumes]
|
156
|
+
puts "[*] Deleting all dangling docker volumes".cyan
|
157
|
+
if options[:'include-named']
|
158
|
+
command = 'docker volume ls -qf dangling=true'
|
159
|
+
else
|
160
|
+
command = "docker volume ls -qf dangling=true | grep -E --color=none '^[A-Fa-f0-9]{64}$'"
|
161
|
+
end
|
162
|
+
volumes = DockerJanitor::Sh.run_command(command).stdout.split("\n")
|
163
|
+
if volumes.empty?
|
164
|
+
puts "[*] No dangling volumes to delete!".green
|
165
|
+
else
|
166
|
+
puts "[*] Removing these volumes:".green
|
167
|
+
volumes.each{ |v| puts v.blue }
|
168
|
+
if options[:dryrun]
|
169
|
+
puts "[*] Not actually deleting volumes (dry run)".yellow
|
170
|
+
else
|
171
|
+
if del_volumes(volumes).success?
|
172
|
+
puts "[*] Succeeded removing dangling volumes".green
|
173
|
+
else
|
174
|
+
puts "[*] Error removing dangling volumes".red
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
puts "Mount Name:\tMount Point:\tFree Before:\tFree After:\tTotal freed:".cyan
|
181
|
+
mounts.each do |m|
|
182
|
+
m.mount.name
|
183
|
+
m.mount.mount_point
|
184
|
+
m.mbfree
|
185
|
+
puts "#{m.mount.name}\t" \
|
186
|
+
"#{m.mount.mount_point}\t\t" \
|
187
|
+
"#{free_space(m.mbfree)}\t\t" \
|
188
|
+
"#{free_space(mb_free(m.mount.mount_point))}\t\t" \
|
189
|
+
"#{free_space(mb_free(m.mount.mount_point) - m.mbfree)}"
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def del_images
|
194
|
+
DockerJanitor::Sh.run_command(
|
195
|
+
"docker rmi $(docker images | grep -iE '^<none>' " \
|
196
|
+
"| awk '{print $3}' | xargs)"
|
197
|
+
)
|
198
|
+
end
|
199
|
+
|
200
|
+
def del_volumes(volumes)
|
201
|
+
DockerJanitor::Sh.run_command(
|
202
|
+
"docker volume rm #{volumes.join(' ')}"
|
203
|
+
)
|
204
|
+
end
|
205
|
+
|
206
|
+
def free_space(megabytes)
|
207
|
+
if megabytes / 1024 > 9
|
208
|
+
"#{megabytes / 1024} GB"
|
209
|
+
else
|
210
|
+
"#{megabytes} MB"
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def mb_free(mount_point)
|
215
|
+
Sys::Filesystem.stat(mount_point).bytes_free / 1024 / 1024
|
216
|
+
end
|
217
|
+
|
218
|
+
def exec_config_file(options)
|
219
|
+
if File.exist?(DockerJanitor::Config.filename)
|
220
|
+
print "A config file already exists at #{DockerJanitor::Config.filename}" \
|
221
|
+
". Overwrite with default settings? (Y/N): ".yellow
|
222
|
+
conf = STDIN.gets.chomp
|
223
|
+
unless conf =~ /y/i
|
224
|
+
puts "User declined over-writing. Not writing config file".red
|
225
|
+
return
|
226
|
+
end
|
227
|
+
end
|
228
|
+
puts "Writing config file to #{DockerJanitor::Config.filename}".green
|
229
|
+
DockerJanitor::Config.write_default_settings_file
|
230
|
+
end
|
231
|
+
|
232
|
+
end
|
233
|
+
|
234
|
+
if !ARGV.empty? && %w[-v --version].include?(ARGV.first)
|
235
|
+
puts "Docker Janitor - Version: #{DockerJanitor::VERSION}"
|
236
|
+
else
|
237
|
+
DockerJanitorBin.start(ARGV)
|
238
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module DockerJanitor
|
4
|
+
class Config
|
5
|
+
def self.filename
|
6
|
+
"#{Dir.home}/.docker-janitor.yml"
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.default_yaml
|
10
|
+
%q(---
|
11
|
+
:docker-janitor:
|
12
|
+
# safe-containers is an array of regular expressions
|
13
|
+
# against which container names will be checked to see if
|
14
|
+
# they are protected. This can help to make sure that
|
15
|
+
# data-only containers for example are not
|
16
|
+
# unintentionally deleted. These are ruby flavored
|
17
|
+
# regular expressions, check with the ruby operator =~
|
18
|
+
# rubular.com is a helpful site for testing your regex
|
19
|
+
:safe_containers:
|
20
|
+
- db$
|
21
|
+
- postgres
|
22
|
+
- mongo
|
23
|
+
- dynamo
|
24
|
+
- mysql
|
25
|
+
:safe_images:
|
26
|
+
- a-sha-of-some-sort
|
27
|
+
:safe_volumes:
|
28
|
+
- a-sha-of-some-sort
|
29
|
+
).split("\n").map{|s| s.sub(' ' * 8, '')}.join("\n")
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.default_settings
|
33
|
+
YAML.load(self.default_yaml)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.settings(filename = self.filename)
|
37
|
+
if File.exist?(filename)
|
38
|
+
self.default_settings.merge(YAML.load_file(filename))
|
39
|
+
else
|
40
|
+
self.default_settings
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.write_settings(settings, filename = self.filename)
|
45
|
+
File.write(filename, settings)
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.write_default_settings_file(filename = self.filename)
|
49
|
+
self.write_settings(self.default_yaml, filename)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module DockerJanitor
|
4
|
+
module Sh
|
5
|
+
def self.run_command(command)
|
6
|
+
stdout = `#{command}`
|
7
|
+
OpenStruct.new({
|
8
|
+
success?: $?.exitstatus == 0,
|
9
|
+
exitstatus: $?.exitstatus,
|
10
|
+
stdout: stdout
|
11
|
+
})
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module Bash
|
16
|
+
def self.run_command(command)
|
17
|
+
stdout = `bash -c "#{command}"`
|
18
|
+
OpenStruct.new({
|
19
|
+
success?: $?.exitstatus == 0,
|
20
|
+
exitstatus: $?.exitstatus,
|
21
|
+
stdout: stdout
|
22
|
+
})
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: docker-janitor
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ben Porter
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-07-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: colorize
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.7'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.7'
|
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'
|
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'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: sys-filesystem
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.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.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: '4.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '4.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.4'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.4'
|
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.5'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '10.5'
|
97
|
+
description: 'The slack web api is good, but very raw. '
|
98
|
+
email: BenjaminPorter86@gmail.com
|
99
|
+
executables:
|
100
|
+
- docker-janitor
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- bin/docker-janitor
|
105
|
+
- lib/docker_janitor.rb
|
106
|
+
- lib/docker_janitor/config.rb
|
107
|
+
- lib/docker_janitor/shell.rb
|
108
|
+
- lib/docker_janitor/version.rb
|
109
|
+
homepage: https://github.com/FreedomBen/docker-janitor
|
110
|
+
licenses:
|
111
|
+
- MIT
|
112
|
+
metadata: {}
|
113
|
+
post_install_message:
|
114
|
+
rdoc_options: []
|
115
|
+
require_paths:
|
116
|
+
- lib
|
117
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
127
|
+
requirements: []
|
128
|
+
rubyforge_project:
|
129
|
+
rubygems_version: 2.5.2
|
130
|
+
signing_key:
|
131
|
+
specification_version: 4
|
132
|
+
summary: docker janitor helps you clean up after the sometimes messy house guest known
|
133
|
+
as docker
|
134
|
+
test_files: []
|