gitlab-janitor 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+
6
+
7
+
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
+ ```
@@ -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
+
@@ -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,5 @@
1
+ module GitlabJanitor
2
+
3
+ VERSION = '0.0.3'.freeze
4
+
5
+ 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
@@ -0,0 +1,5 @@
1
+ require_relative 'gitlab-janitor/version'
2
+ require_relative 'gitlab-janitor/utils'
3
+ require_relative 'gitlab-janitor/base-cleaner'
4
+ require_relative 'gitlab-janitor/container-cleaner'
5
+ require_relative 'gitlab-janitor/volume-cleaner'
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: []