gitlab-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/Dockerfile +33 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +86 -0
- data/LICENSE +19 -0
- data/README.md +52 -0
- data/bin/gitlab-janitor +104 -0
- data/docker-compose.yml +21 -0
- data/gitlab-janitor.gemspec +33 -0
- data/lib/gitlab-janitor/base-cleaner.rb +50 -0
- data/lib/gitlab-janitor/container-cleaner.rb +114 -0
- data/lib/gitlab-janitor/utils.rb +69 -0
- data/lib/gitlab-janitor/version.rb +5 -0
- data/lib/gitlab-janitor/volume-cleaner.rb +90 -0
- data/lib/gitlab-janitor.rb +5 -0
- metadata +220 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 20a74b385cc01ef9b3fcaf380a1186f3f8bb24b278aa496d38cfc859889301bc
|
4
|
+
data.tar.gz: 2488753090d283ed78422395dded9e8935f060285987b032aae9350b3aecb3b4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 39f830eb28f777462808abea43295d2e7297eed919028661bbfae9d213eb45f2c2e66ed3daea021fb48a790186bb2c678d287b0881c8580f82e5fc203ee650fa
|
7
|
+
data.tar.gz: cdbbeaa361c77f3aeb5deb707000c2ca93e0c0d26381f5c6b2be63c24917e95586b834fa17bcddfe15a80607c736be1961048c18ce75a7606fe306927877e82f
|
data/Dockerfile
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
ARG RUBY_VERSION=3.1
|
2
|
+
|
3
|
+
FROM ruby:${RUBY_VERSION}-alpine
|
4
|
+
|
5
|
+
WORKDIR /home/app
|
6
|
+
|
7
|
+
RUN mkdir -p /usr/local/etc \
|
8
|
+
&& { \
|
9
|
+
echo 'install: --no-document'; \
|
10
|
+
echo 'update: --no-document'; \
|
11
|
+
} >> /usr/local/etc/gemrc \
|
12
|
+
&& echo 'gem: --no-document' > ~/.gemrc
|
13
|
+
|
14
|
+
# RUN set -ex \
|
15
|
+
# && apk add --no-cache build-base git curl
|
16
|
+
|
17
|
+
ADD Gemfile Gemfile.lock /home/app/
|
18
|
+
|
19
|
+
RUN set -ex \
|
20
|
+
&& gem install bundler && gem update bundler \
|
21
|
+
&& bundle config set --local system 'true' \
|
22
|
+
&& bundle install --jobs=3 \
|
23
|
+
&& rm -rf /tmp/* /var/tmp/* /usr/src/ruby /root/.gem /usr/local/bundle/cache
|
24
|
+
|
25
|
+
ADD . /home/app/
|
26
|
+
|
27
|
+
RUN set -ex \
|
28
|
+
&& bundle install --jobs=3 \
|
29
|
+
&& rm -rf /tmp/* /var/tmp/* /usr/src/ruby /root/.gem /usr/local/bundle/cache
|
30
|
+
|
31
|
+
CMD ["bundle", "exec", "giltab-janitor"]
|
32
|
+
|
33
|
+
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
gitlab-janitor (0.0.3)
|
5
|
+
activesupport (~> 6.0)
|
6
|
+
docker-api
|
7
|
+
fugit
|
8
|
+
optparse
|
9
|
+
|
10
|
+
GEM
|
11
|
+
remote: https://rubygems.org/
|
12
|
+
specs:
|
13
|
+
activesupport (6.1.6.1)
|
14
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
15
|
+
i18n (>= 1.6, < 2)
|
16
|
+
minitest (>= 5.1)
|
17
|
+
tzinfo (~> 2.0)
|
18
|
+
zeitwerk (~> 2.3)
|
19
|
+
ansi (1.5.0)
|
20
|
+
awesome_print (1.9.2)
|
21
|
+
concurrent-ruby (1.1.10)
|
22
|
+
diff-lcs (1.5.0)
|
23
|
+
docile (1.4.0)
|
24
|
+
docker-api (2.2.0)
|
25
|
+
excon (>= 0.47.0)
|
26
|
+
multi_json
|
27
|
+
et-orbi (1.2.7)
|
28
|
+
tzinfo
|
29
|
+
excon (0.92.4)
|
30
|
+
fugit (1.5.3)
|
31
|
+
et-orbi (~> 1, >= 1.2.7)
|
32
|
+
raabro (~> 1.4)
|
33
|
+
i18n (1.12.0)
|
34
|
+
concurrent-ruby (~> 1.0)
|
35
|
+
minitest (5.16.2)
|
36
|
+
multi_json (1.15.0)
|
37
|
+
optparse (0.2.0)
|
38
|
+
raabro (1.4.0)
|
39
|
+
rake (13.0.6)
|
40
|
+
rspec (3.11.0)
|
41
|
+
rspec-core (~> 3.11.0)
|
42
|
+
rspec-expectations (~> 3.11.0)
|
43
|
+
rspec-mocks (~> 3.11.0)
|
44
|
+
rspec-core (3.11.0)
|
45
|
+
rspec-support (~> 3.11.0)
|
46
|
+
rspec-expectations (3.11.0)
|
47
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
48
|
+
rspec-support (~> 3.11.0)
|
49
|
+
rspec-mocks (3.11.1)
|
50
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
51
|
+
rspec-support (~> 3.11.0)
|
52
|
+
rspec-support (3.11.0)
|
53
|
+
rspec_junit_formatter (0.5.1)
|
54
|
+
rspec-core (>= 2, < 4, != 2.12.0)
|
55
|
+
simplecov (0.21.2)
|
56
|
+
docile (~> 1.1)
|
57
|
+
simplecov-html (~> 0.11)
|
58
|
+
simplecov_json_formatter (~> 0.1)
|
59
|
+
simplecov-console (0.9.1)
|
60
|
+
ansi
|
61
|
+
simplecov
|
62
|
+
terminal-table
|
63
|
+
simplecov-html (0.12.3)
|
64
|
+
simplecov_json_formatter (0.1.4)
|
65
|
+
terminal-table (3.0.2)
|
66
|
+
unicode-display_width (>= 1.1.1, < 3)
|
67
|
+
tzinfo (2.0.5)
|
68
|
+
concurrent-ruby (~> 1.0)
|
69
|
+
unicode-display_width (2.2.0)
|
70
|
+
zeitwerk (2.6.0)
|
71
|
+
|
72
|
+
PLATFORMS
|
73
|
+
x86_64-linux
|
74
|
+
|
75
|
+
DEPENDENCIES
|
76
|
+
awesome_print
|
77
|
+
bundler (~> 2.0, >= 2.0.1)
|
78
|
+
gitlab-janitor!
|
79
|
+
rake
|
80
|
+
rspec
|
81
|
+
rspec_junit_formatter
|
82
|
+
simplecov
|
83
|
+
simplecov-console
|
84
|
+
|
85
|
+
BUNDLED WITH
|
86
|
+
2.3.15
|
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2014-2019 Рнд Софт
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# gitlab-janitor
|
2
|
+
|
3
|
+
GitLab Janitor is a tool to automatically manage stalled containers when using Docker.
|
4
|
+
|
5
|
+
Commain line options and default valuee:
|
6
|
+
|
7
|
+
```bash
|
8
|
+
$ ./gitlab-janitor.rb --help
|
9
|
+
|
10
|
+
Usage: gitlab-janitor.rb [options]
|
11
|
+
--clean-delay=30m Delay between clean operation ENV[CLEAN_DELAY]
|
12
|
+
--include=*units* <List> Include container for removal. ENV[INCLUDE]
|
13
|
+
--exclude=*gitlab* <List> Exclude container from removal by name. ENV[EXCLUDE]
|
14
|
+
--container-deadline=1s Maximum container run duration. ENV[CONTAINER_DEADLINE]
|
15
|
+
--volume-deadline=2d6h Maximum volume life dudation. ENV[VOLUME_DEADLINE]
|
16
|
+
--image-deadline=20d Maximum image life duration. ENV[IMAGE_DEADLINE]
|
17
|
+
--remove Real remove instead of dry run. ENV[REMOVE]
|
18
|
+
--docker=unix:///var/run/docker.sock
|
19
|
+
Docker api endpoint. ENV[DOCKER_HOST]
|
20
|
+
```
|
21
|
+
|
22
|
+
|
23
|
+
## Удаление зависших контейнеров
|
24
|
+
|
25
|
+
Порядок определения контейнреов для удаления:
|
26
|
+
|
27
|
+
- `include=*units*` - в список на удаление включаются контейнеры удовлетворябющие шаблону;
|
28
|
+
- `exclude=*gitlab*` - из спсика исключаются контейнеры по шаблону;
|
29
|
+
- `container-deadline=[1h10m]` - результирующий список проверяется на длительность запуска контенйра;
|
30
|
+
|
31
|
+
## Удаление ненужных volumes
|
32
|
+
|
33
|
+
Порядок определения вольюмов для удалени:
|
34
|
+
|
35
|
+
- на удаление попадают только вольюмы, не являющиеся именованными;
|
36
|
+
- `volume-deadline=[2d6h]` - результирующий список проверяется на длительность существования вольюма;
|
37
|
+
|
38
|
+
## Удаление образов
|
39
|
+
|
40
|
+
Docker не сохраняет временную метку образа при скачивании (pull), там образом используя средства Docker API невозможно понять как давно образ был скачан и когда его поря удалять. Для решения этой задачи сервис сохраняет информацию о скачанных образах, отслеживая там образом интервалы устаревания.
|
41
|
+
|
42
|
+
Порядок определения образов для удалени:
|
43
|
+
|
44
|
+
- на удаление попадают только образы, не имеющие тэг `latest`;
|
45
|
+
- `image-deadline=[20d]` - результирующий список проверяется на длительность существования образа;
|
46
|
+
|
47
|
+
|
48
|
+
## Пример запуска
|
49
|
+
|
50
|
+
```bash
|
51
|
+
REMOVE=true INCLUDE="*integr*, *units*" EXCLUDE="*gitlab*" CONTAINER_DEADLINE="1h10m" VOLUME_DEADLINE="3d" IMAGE_DEADLINE="20d" ./main.rb
|
52
|
+
```
|
data/bin/gitlab-janitor
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
#cwd = __dir__
|
4
|
+
#$root = "#{cwd}/"
|
5
|
+
#$: << $root
|
6
|
+
|
7
|
+
#lib = File.expand_path('lib', __dir__)
|
8
|
+
#$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
9
|
+
|
10
|
+
|
11
|
+
#require 'rubygems'
|
12
|
+
#require 'bundler'
|
13
|
+
#require 'bundler/setup'
|
14
|
+
#Bundler.require(:default)
|
15
|
+
|
16
|
+
require 'active_support/all'
|
17
|
+
require 'docker-api'
|
18
|
+
require 'fugit'
|
19
|
+
require 'optparse'
|
20
|
+
|
21
|
+
require 'gitlab-janitor'
|
22
|
+
|
23
|
+
UTIL = File.basename(__FILE__)
|
24
|
+
|
25
|
+
GitlabJanitor::Util.setup
|
26
|
+
|
27
|
+
@opts = {
|
28
|
+
includes: [ENV.fetch('INCLUDE', '*units*')],
|
29
|
+
excludes: [ENV.fetch('EXCLUDE', '*gitlab*')],
|
30
|
+
clean_delay: ENV.fetch('CLEAN_DELAY', '30m'),
|
31
|
+
container_deadline: ENV.fetch('CONTAINER_DEADLINE', '1h10m'),
|
32
|
+
volume_deadline: ENV.fetch('VOLUME_DEADLINE', '2d6h'),
|
33
|
+
image_deadline: ENV.fetch('IMAGE_DEADLINE', '20d'),
|
34
|
+
remove: ENV.fetch('REMOVE', 'false').to_bool,
|
35
|
+
docker_host: ENV.fetch('DOCKER_HOST', 'unix:///var/run/docker.sock')
|
36
|
+
}
|
37
|
+
|
38
|
+
parser = OptionParser.new do |o|
|
39
|
+
o.banner = "Usage: #{UTIL} [options] "
|
40
|
+
|
41
|
+
o.on("--clean-delay=#{@opts[:clean_delay]}", 'Delay between clean operation ENV[CLEAN_DELAY]') do |pattern|
|
42
|
+
@opts[:clean_delay] = pattern.strip
|
43
|
+
end
|
44
|
+
|
45
|
+
o.on("--include=#{@opts[:includes].join(',')}", '<List> Include container for removal. ENV[INCLUDE]') do |pattern|
|
46
|
+
@opts[:includes] += pattern.split(/[,;]/)
|
47
|
+
end
|
48
|
+
|
49
|
+
o.on("--exclude=#{@opts[:excludes].join(',')}", '<List> Exclude container from removal by name. ENV[EXCLUDE]') do |pattern|
|
50
|
+
@opts[:excludes] += pattern.split(/[,;]/)
|
51
|
+
end
|
52
|
+
|
53
|
+
o.on("--container-deadline=#{@opts[:container_deadline]}", 'Maximum container run duration. ENV[CONTAINER_DEADLINE]') do |pattern|
|
54
|
+
@opts[:container_deadline] = pattern.strip
|
55
|
+
end
|
56
|
+
|
57
|
+
o.on("--volume-deadline=#{@opts[:volume_deadline]}", 'Maximum volume life dudation. ENV[VOLUME_DEADLINE]') do |pattern|
|
58
|
+
@opts[:volume_deadline] = pattern.strip
|
59
|
+
end
|
60
|
+
|
61
|
+
o.on("--image-deadline=#{@opts[:image_deadline]}", 'Maximum image life duration. ENV[IMAGE_DEADLINE]') do |pattern|
|
62
|
+
@opts[:image_deadline] = pattern.strip
|
63
|
+
end
|
64
|
+
|
65
|
+
o.on("--remove", 'Real remove instead of dry run. ENV[REMOVE]') do |value|
|
66
|
+
@opts[:remove] = value.strip.to_bool
|
67
|
+
end
|
68
|
+
|
69
|
+
o.on("--docker=#{@opts[:docker_host]}", 'Docker api endpoint. ENV[DOCKER_HOST]') do |url|
|
70
|
+
@opts[:docker_host] = value.strip
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
parser.parse!
|
75
|
+
|
76
|
+
Docker.url = @opts[:docker_host]
|
77
|
+
|
78
|
+
GitlabJanitor::Util::logger.debug do
|
79
|
+
"Config: #{JSON.pretty_generate(@opts)}"
|
80
|
+
end
|
81
|
+
|
82
|
+
containers = GitlabJanitor::ContainerCleaner.new(
|
83
|
+
delay: Fugit::Duration.parse(@opts[:clean_delay]).to_sec,
|
84
|
+
includes: @opts[:includes],
|
85
|
+
excludes: @opts[:excludes],
|
86
|
+
deadline: Fugit::Duration.parse(@opts[:container_deadline]).to_sec
|
87
|
+
)
|
88
|
+
|
89
|
+
volumes = GitlabJanitor::VolumeCleaner.new(
|
90
|
+
delay: Fugit::Duration.parse(@opts[:clean_delay]).to_sec,
|
91
|
+
deadline: Fugit::Duration.parse(@opts[:volume_deadline]).to_sec
|
92
|
+
)
|
93
|
+
|
94
|
+
|
95
|
+
while !$exiting do
|
96
|
+
containers.clean(remove: @opts[:remove])
|
97
|
+
volumes.clean(remove: @opts[:remove])
|
98
|
+
|
99
|
+
sleep 3
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
|
104
|
+
|
data/docker-compose.yml
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
version: "2.3"
|
2
|
+
|
3
|
+
services:
|
4
|
+
gitlab-janitor:
|
5
|
+
restart: 'unless-stopped'
|
6
|
+
healthcheck:
|
7
|
+
disable: true
|
8
|
+
build:
|
9
|
+
context: .
|
10
|
+
args:
|
11
|
+
RUBY_VERSION: 3.1
|
12
|
+
image: ${SERVICE_IMAGE-rnds/gitlab-janitor}:${SERVICE_TAG-latest}
|
13
|
+
working_dir: /home/app
|
14
|
+
tmpfs: /tmp
|
15
|
+
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
lib = File.expand_path('lib', __dir__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
|
4
|
+
# Maintain your gem's version:
|
5
|
+
require 'gitlab-janitor/version'
|
6
|
+
|
7
|
+
Gem::Specification.new 'gitlab-janitor' do |spec|
|
8
|
+
spec.version = ENV['BUILDVERSION'].to_i > 0 ? "#{Lusnoc::VERSION}.#{ENV['BUILDVERSION'].to_i}" : GitlabJanitor::VERSION
|
9
|
+
spec.authors = ['Samoilenko Yuri']
|
10
|
+
spec.email = ['kinnalru@gmail.com']
|
11
|
+
spec.description = spec.summary = 'GitLab Janitor is a tool to automatically manage stalled containers when using Docker.'
|
12
|
+
spec.homepage = 'https://github.com/RnD-Soft/gitlab-janitor'
|
13
|
+
spec.license = 'MIT'
|
14
|
+
|
15
|
+
spec.files = Dir['bin/**/*', 'lib/**/*', 'Gemfile*', 'LICENSE', 'README.md', 'Dockerfile*', 'docker-compose.yml', '*.gemspec']
|
16
|
+
spec.bindir = 'bin'
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/gitlab-janitor}) {|f| File.basename(f) }
|
18
|
+
spec.require_paths = ['lib']
|
19
|
+
|
20
|
+
spec.add_development_dependency 'bundler', '~> 2.0', '>= 2.0.1'
|
21
|
+
spec.add_development_dependency 'rake'
|
22
|
+
spec.add_development_dependency 'rspec'
|
23
|
+
spec.add_development_dependency 'rspec_junit_formatter'
|
24
|
+
spec.add_development_dependency 'simplecov'
|
25
|
+
spec.add_development_dependency 'simplecov-console'
|
26
|
+
spec.add_development_dependency 'awesome_print'
|
27
|
+
|
28
|
+
spec.add_runtime_dependency 'activesupport', '~> 6.0'
|
29
|
+
spec.add_runtime_dependency 'docker-api'
|
30
|
+
spec.add_runtime_dependency 'fugit'
|
31
|
+
spec.add_runtime_dependency 'optparse'
|
32
|
+
end
|
33
|
+
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module GitlabJanitor
|
2
|
+
class BaseCleaner
|
3
|
+
|
4
|
+
class Model
|
5
|
+
attr_reader :model
|
6
|
+
def initialize(m)
|
7
|
+
@model = m
|
8
|
+
end
|
9
|
+
|
10
|
+
def method_missing(method, *args, &block)
|
11
|
+
super unless model.respond_to?(method)
|
12
|
+
|
13
|
+
model.send(method, *args, &block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def respond_to?(*args)
|
17
|
+
model.send(:respond_to?, *args) || super
|
18
|
+
end
|
19
|
+
|
20
|
+
def respond_to_missing?(method_name, include_private = false)
|
21
|
+
model.send(:respond_to_missing?, method_name, include_private) || super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :delay, :deadline, :logger
|
26
|
+
|
27
|
+
def initialize delay: 10, deadline: 1.second, logger: GitlabJanitor::Util::logger, **args
|
28
|
+
@delay = delay
|
29
|
+
@deadline = deadline
|
30
|
+
@logger = logger.tagged(self.class.to_s)
|
31
|
+
end
|
32
|
+
|
33
|
+
def clean(remove: false)
|
34
|
+
return nil if @cleaned_at && (::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - @cleaned_at) < @delay.seconds
|
35
|
+
|
36
|
+
do_clean(remove: remove)
|
37
|
+
|
38
|
+
@cleaned_at = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
39
|
+
return true
|
40
|
+
end
|
41
|
+
|
42
|
+
def log_exception(text)
|
43
|
+
yield
|
44
|
+
rescue StandardError => e
|
45
|
+
logger.error("Exception in #{text}: #{e}")
|
46
|
+
logger.error e.backtrace
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module GitlabJanitor
|
2
|
+
class ContainerCleaner < BaseCleaner
|
3
|
+
|
4
|
+
class Model < BaseCleaner::Model
|
5
|
+
def initialize(v)
|
6
|
+
super(v)
|
7
|
+
|
8
|
+
info['_Age'] = (Time.now - Time.at(created_at)).round(0)
|
9
|
+
end
|
10
|
+
|
11
|
+
def created_at
|
12
|
+
info['Created']
|
13
|
+
end
|
14
|
+
|
15
|
+
def name
|
16
|
+
@anme ||= info['Names'].first.sub(/^\//, '')
|
17
|
+
end
|
18
|
+
|
19
|
+
def age
|
20
|
+
info['_Age']
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :excludes, :includes
|
25
|
+
|
26
|
+
def initialize includes: [''], excludes: [''], **args
|
27
|
+
super(**args)
|
28
|
+
@includes = includes
|
29
|
+
@excludes = excludes
|
30
|
+
@deadline = deadline
|
31
|
+
end
|
32
|
+
|
33
|
+
def do_clean(remove: false)
|
34
|
+
to_remove, keep = prepare(Docker::Container.all(all: true).map{|m| Model.new(m)})
|
35
|
+
|
36
|
+
if !to_remove.empty?
|
37
|
+
keep.each do |c|
|
38
|
+
logger.debug(" KEEP #{c.name}")
|
39
|
+
end
|
40
|
+
|
41
|
+
if remove
|
42
|
+
logger.info "Removing containers..."
|
43
|
+
to_remove.each do |c|
|
44
|
+
logger.tagged(c.name) do
|
45
|
+
logger.debug " Removing..."
|
46
|
+
log_exception("Stop") {c.stop}
|
47
|
+
log_exception("Wait") {c.wait(15)}
|
48
|
+
log_exception("Remove") {c.remove}
|
49
|
+
logger.debug " Removing COMPLETED"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
else
|
53
|
+
logger.info "Skip removal due to dry run"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
def prepare containers
|
60
|
+
@logger.debug("Selecting containers by includes #{@includes}...")
|
61
|
+
to_remove = select_by_name(containers)
|
62
|
+
if to_remove.empty?
|
63
|
+
@logger.info("Noting to remove.")
|
64
|
+
return [], containers
|
65
|
+
end
|
66
|
+
@logger.info("Selected containers: \n#{to_remove.map{|c| " + #{format_item(c)}"}.join("\n")}")
|
67
|
+
|
68
|
+
@logger.debug("Filtering containers by excludes #{@excludes}...")
|
69
|
+
to_remove = reject_by_name(to_remove)
|
70
|
+
if to_remove.empty?
|
71
|
+
@logger.info("Noting to remove.")
|
72
|
+
return [], containers
|
73
|
+
end
|
74
|
+
@logger.info("Filtered containers: \n#{to_remove.map{|c| " + #{format_item(c)}"}.join("\n")}")
|
75
|
+
|
76
|
+
@logger.debug("Filtering containers by deadline: older than #{Fugit::Duration.parse(@deadline).deflate.to_plain_s}...")
|
77
|
+
to_remove = select_by_deadline(to_remove)
|
78
|
+
if to_remove.empty?
|
79
|
+
@logger.info("Noting to remove.")
|
80
|
+
return [], containers
|
81
|
+
end
|
82
|
+
@logger.info("Filtered containers: \n#{to_remove.map{|c| " + #{format_item(c)}"}.join("\n")}")
|
83
|
+
|
84
|
+
[to_remove, containers - to_remove]
|
85
|
+
end
|
86
|
+
|
87
|
+
def format_item c
|
88
|
+
"#{Time.at(c.created_at)} Age:#{Fugit::Duration.parse(c.age).deflate.to_plain_s.ljust(10)} #{c.name.first(60).ljust(60)}"
|
89
|
+
end
|
90
|
+
|
91
|
+
def select_by_name containers
|
92
|
+
containers.select do |c|
|
93
|
+
@includes.any? do |pattern|
|
94
|
+
File.fnmatch(pattern, c.name)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def reject_by_name containers
|
100
|
+
containers.reject do |c|
|
101
|
+
@excludes.any? do |pattern|
|
102
|
+
File.fnmatch(pattern, c.name)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def select_by_deadline containers
|
108
|
+
containers.select do |c|
|
109
|
+
c.age > deadline
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module GitlabJanitor
|
2
|
+
|
3
|
+
class Fmt < ActiveSupport::Logger::SimpleFormatter
|
4
|
+
def call(severity, timestamp, progname, msg)
|
5
|
+
super
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class Util
|
10
|
+
|
11
|
+
TERM_SIGNALS = %w[INT TERM].freeze
|
12
|
+
|
13
|
+
class << self
|
14
|
+
|
15
|
+
def exiting?
|
16
|
+
$exiting
|
17
|
+
end
|
18
|
+
|
19
|
+
def exit!
|
20
|
+
$exiting = true
|
21
|
+
end
|
22
|
+
|
23
|
+
def logger
|
24
|
+
$logger
|
25
|
+
end
|
26
|
+
|
27
|
+
def setup
|
28
|
+
STDOUT.sync = true
|
29
|
+
STDERR.sync = true
|
30
|
+
|
31
|
+
$logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
|
32
|
+
$logger.level = ENV.fetch('LOG_LEVEL', Logger::INFO)
|
33
|
+
formatter = Logger::Formatter.new
|
34
|
+
formatter.extend ActiveSupport::TaggedLogging::Formatter
|
35
|
+
$logger.formatter = formatter
|
36
|
+
|
37
|
+
|
38
|
+
|
39
|
+
initialize_signal_handlers
|
40
|
+
|
41
|
+
String.class_eval do
|
42
|
+
def to_bool
|
43
|
+
return true if self == true || self =~ (/(true|t|yes|y|1)$/i)
|
44
|
+
return false if self == false || self.blank? || self =~ (/(false|f|no|n|0)$/i)
|
45
|
+
raise ArgumentError.new("invalid value for Boolean: \"#{self}\"")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def initialize_signal_handlers
|
51
|
+
TERM_SIGNALS.each do |sig|
|
52
|
+
trap(sig) do |*_args|
|
53
|
+
TERM_SIGNALS.each do |sig|
|
54
|
+
trap(sig) do |*_args|
|
55
|
+
STDERR.puts 'Forcing exit!'
|
56
|
+
Kernel::exit!(1)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
STDOUT.puts "Caught signal[#{sig}]: exiting...."
|
61
|
+
GitlabJanitor::Util.exit!
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module GitlabJanitor
|
2
|
+
class VolumeCleaner < BaseCleaner
|
3
|
+
|
4
|
+
class Model < BaseCleaner::Model
|
5
|
+
def initialize(v)
|
6
|
+
super(v)
|
7
|
+
|
8
|
+
info['_Age'] = (Time.now - Time.parse(created_at)).round(0)
|
9
|
+
end
|
10
|
+
|
11
|
+
def created_at
|
12
|
+
info['CreatedAt']
|
13
|
+
end
|
14
|
+
|
15
|
+
def name
|
16
|
+
info['Name']
|
17
|
+
end
|
18
|
+
|
19
|
+
def age
|
20
|
+
info['_Age']
|
21
|
+
end
|
22
|
+
|
23
|
+
def mountpoint
|
24
|
+
info['Mountpoint']
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def do_clean(remove: false)
|
29
|
+
to_remove, keep = prepare(Docker::Volume.all.map{|m| Model.new(m)})
|
30
|
+
|
31
|
+
if !to_remove.empty?
|
32
|
+
keep.each do |c|
|
33
|
+
logger.debug(" KEEP #{c.name}")
|
34
|
+
end
|
35
|
+
if remove
|
36
|
+
logger.info "Removing volumes..."
|
37
|
+
to_remove.each do |c|
|
38
|
+
logger.tagged(c.name.first(10)) do
|
39
|
+
logger.debug " Removing..."
|
40
|
+
log_exception("Remove") {c.remove}
|
41
|
+
logger.debug " Removing COMPLETED"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
else
|
45
|
+
logger.info "Skip removal due to dry run"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
def prepare volumes
|
52
|
+
@logger.debug("Selecting unnamed volumes...")
|
53
|
+
to_remove = select_unnamed(volumes)
|
54
|
+
if to_remove.empty?
|
55
|
+
@logger.info("Noting to remove.")
|
56
|
+
return [], volumes
|
57
|
+
end
|
58
|
+
@logger.info("Selected volumes: \n#{to_remove.map{|c| " + #{format_item(c)}"}.join("\n")}")
|
59
|
+
|
60
|
+
@logger.debug("Filtering volumes by deadline: older than #{@deadline} seconds...")
|
61
|
+
to_remove = select_by_deadline(to_remove)
|
62
|
+
if to_remove.empty?
|
63
|
+
@logger.info("Noting to remove.")
|
64
|
+
return [], volumes
|
65
|
+
end
|
66
|
+
@logger.info("Filtered volumes: \n#{to_remove.map{|c| " !! #{format_item(c)}"}.join("\n")}")
|
67
|
+
|
68
|
+
return to_remove, (volumes - to_remove)
|
69
|
+
end
|
70
|
+
|
71
|
+
def format_item c
|
72
|
+
"#{Time.parse(c.created_at)} Age:#{Fugit::Duration.parse(c.age).deflate.to_plain_s.ljust(13)} #{c.name.first(10).ljust(10)} #{c.mountpoint}"
|
73
|
+
end
|
74
|
+
|
75
|
+
SHA_RX = /^[a-zA-Z0-9]{64}$/
|
76
|
+
|
77
|
+
def select_unnamed volumes
|
78
|
+
volumes.select do |c|
|
79
|
+
SHA_RX.match(c.name)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def select_by_deadline containers
|
84
|
+
containers.select do |c|
|
85
|
+
c.age > deadline
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
metadata
ADDED
@@ -0,0 +1,220 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gitlab-janitor
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Samoilenko Yuri
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-08-01 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: '2.0'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 2.0.1
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '2.0'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 2.0.1
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: rake
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
type: :development
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rspec
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: rspec_junit_formatter
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: simplecov
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: simplecov-console
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
- !ruby/object:Gem::Dependency
|
104
|
+
name: awesome_print
|
105
|
+
requirement: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
type: :development
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
- !ruby/object:Gem::Dependency
|
118
|
+
name: activesupport
|
119
|
+
requirement: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - "~>"
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '6.0'
|
124
|
+
type: :runtime
|
125
|
+
prerelease: false
|
126
|
+
version_requirements: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - "~>"
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '6.0'
|
131
|
+
- !ruby/object:Gem::Dependency
|
132
|
+
name: docker-api
|
133
|
+
requirement: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
type: :runtime
|
139
|
+
prerelease: false
|
140
|
+
version_requirements: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - ">="
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0'
|
145
|
+
- !ruby/object:Gem::Dependency
|
146
|
+
name: fugit
|
147
|
+
requirement: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - ">="
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '0'
|
152
|
+
type: :runtime
|
153
|
+
prerelease: false
|
154
|
+
version_requirements: !ruby/object:Gem::Requirement
|
155
|
+
requirements:
|
156
|
+
- - ">="
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: '0'
|
159
|
+
- !ruby/object:Gem::Dependency
|
160
|
+
name: optparse
|
161
|
+
requirement: !ruby/object:Gem::Requirement
|
162
|
+
requirements:
|
163
|
+
- - ">="
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
type: :runtime
|
167
|
+
prerelease: false
|
168
|
+
version_requirements: !ruby/object:Gem::Requirement
|
169
|
+
requirements:
|
170
|
+
- - ">="
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: '0'
|
173
|
+
description: GitLab Janitor is a tool to automatically manage stalled containers when
|
174
|
+
using Docker.
|
175
|
+
email:
|
176
|
+
- kinnalru@gmail.com
|
177
|
+
executables:
|
178
|
+
- gitlab-janitor
|
179
|
+
extensions: []
|
180
|
+
extra_rdoc_files: []
|
181
|
+
files:
|
182
|
+
- Dockerfile
|
183
|
+
- Gemfile
|
184
|
+
- Gemfile.lock
|
185
|
+
- LICENSE
|
186
|
+
- README.md
|
187
|
+
- bin/gitlab-janitor
|
188
|
+
- docker-compose.yml
|
189
|
+
- gitlab-janitor.gemspec
|
190
|
+
- lib/gitlab-janitor.rb
|
191
|
+
- lib/gitlab-janitor/base-cleaner.rb
|
192
|
+
- lib/gitlab-janitor/container-cleaner.rb
|
193
|
+
- lib/gitlab-janitor/utils.rb
|
194
|
+
- lib/gitlab-janitor/version.rb
|
195
|
+
- lib/gitlab-janitor/volume-cleaner.rb
|
196
|
+
homepage: https://github.com/RnD-Soft/gitlab-janitor
|
197
|
+
licenses:
|
198
|
+
- MIT
|
199
|
+
metadata: {}
|
200
|
+
post_install_message:
|
201
|
+
rdoc_options: []
|
202
|
+
require_paths:
|
203
|
+
- lib
|
204
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - ">="
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '0'
|
209
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
210
|
+
requirements:
|
211
|
+
- - ">="
|
212
|
+
- !ruby/object:Gem::Version
|
213
|
+
version: '0'
|
214
|
+
requirements: []
|
215
|
+
rubygems_version: 3.3.8
|
216
|
+
signing_key:
|
217
|
+
specification_version: 4
|
218
|
+
summary: GitLab Janitor is a tool to automatically manage stalled containers when
|
219
|
+
using Docker.
|
220
|
+
test_files: []
|