gitlab-janitor 0.0.3 → 1.0.2.92939
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 +4 -4
- data/Dockerfile +29 -4
- data/Gemfile +0 -3
- data/Gemfile.lock +7 -2
- data/README.md +150 -31
- data/bin/gitlab-janitor +80 -32
- data/docker-compose.yml +4 -0
- data/gitlab-janitor.gemspec +7 -4
- data/lib/gitlab-janitor.rb +2 -5
- data/lib/{gitlab-janitor/base-cleaner.rb → gitlab_janitor/base_cleaner.rb} +13 -5
- data/lib/gitlab_janitor/cache_cleaner.rb +24 -0
- data/lib/gitlab_janitor/container_cleaner.rb +120 -0
- data/lib/gitlab_janitor/image_cleaner/store.rb +66 -0
- data/lib/gitlab_janitor/image_cleaner.rb +140 -0
- data/lib/{gitlab-janitor → gitlab_janitor}/utils.rb +16 -21
- data/lib/gitlab_janitor/version.rb +6 -0
- data/lib/gitlab_janitor/volume_cleaner.rb +120 -0
- data/lib/gitlab_janitor.rb +8 -0
- metadata +50 -18
- data/lib/gitlab-janitor/container-cleaner.rb +0 -114
- data/lib/gitlab-janitor/version.rb +0 -5
- data/lib/gitlab-janitor/volume-cleaner.rb +0 -90
@@ -0,0 +1,120 @@
|
|
1
|
+
module GitlabJanitor
|
2
|
+
class ContainerCleaner < BaseCleaner
|
3
|
+
|
4
|
+
class Model < BaseCleaner::Model
|
5
|
+
|
6
|
+
def initialize(model)
|
7
|
+
super(model)
|
8
|
+
|
9
|
+
info['_Age'] = (Time.now - Time.at(created_at)).round(0)
|
10
|
+
end
|
11
|
+
|
12
|
+
def created_at
|
13
|
+
info['Created']
|
14
|
+
end
|
15
|
+
|
16
|
+
def name
|
17
|
+
@name ||= info['Names'].first.sub(%r{^/}, '')
|
18
|
+
end
|
19
|
+
|
20
|
+
def age
|
21
|
+
info['_Age']
|
22
|
+
end
|
23
|
+
|
24
|
+
def age_text
|
25
|
+
Fugit::Duration.parse(age).deflate.to_plain_s
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_reader :excludes, :includes
|
31
|
+
|
32
|
+
def initialize(includes: [''], excludes: [''], **kwargs)
|
33
|
+
super(**kwargs)
|
34
|
+
@includes = includes
|
35
|
+
@excludes = excludes
|
36
|
+
@deadline = deadline
|
37
|
+
end
|
38
|
+
|
39
|
+
def do_clean(remove: false)
|
40
|
+
to_remove, keep = prepare(Docker::Container.all(all: true).map{|m| Model.new(m) })
|
41
|
+
|
42
|
+
return if to_remove.empty?
|
43
|
+
|
44
|
+
keep.each {|m| logger.debug(" KEEP #{m.name}") }
|
45
|
+
|
46
|
+
if remove
|
47
|
+
logger.info 'Removing containers...'
|
48
|
+
to_remove.each do |model|
|
49
|
+
return false if exiting?
|
50
|
+
|
51
|
+
logger.tagged(model.name) do
|
52
|
+
logger.debug ' Removing...'
|
53
|
+
log_exception('Stop') { model.stop }
|
54
|
+
log_exception('Wait') { model.wait(15) }
|
55
|
+
log_exception('Remove') { model.remove }
|
56
|
+
logger.debug ' Removing COMPLETED'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
else
|
60
|
+
logger.info 'Skip removal due to dry run'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def prepare(containers)
|
65
|
+
@logger.debug("Selecting containers by includes #{@includes}...")
|
66
|
+
to_remove = select_by_name(containers)
|
67
|
+
if to_remove.empty?
|
68
|
+
@logger.info('Noting to remove.')
|
69
|
+
return [], containers
|
70
|
+
end
|
71
|
+
@logger.info("Selected containers: \n#{to_remove.map{|c| " + #{format_item(c)}" }.join("\n")}")
|
72
|
+
|
73
|
+
@logger.debug("Filtering containers by excludes #{@excludes}...")
|
74
|
+
to_remove = reject_by_name(to_remove)
|
75
|
+
if to_remove.empty?
|
76
|
+
@logger.info('Noting to remove.')
|
77
|
+
return [], containers
|
78
|
+
end
|
79
|
+
@logger.info("Filtered containers: \n#{to_remove.map{|c| " + #{format_item(c)}" }.join("\n")}")
|
80
|
+
|
81
|
+
@logger.info("Filtering containers by deadline: older than #{Fugit::Duration.parse(@deadline).deflate.to_plain_s}...")
|
82
|
+
to_remove = select_by_deadline(to_remove)
|
83
|
+
if to_remove.empty?
|
84
|
+
@logger.info('Noting to remove.')
|
85
|
+
return [], containers
|
86
|
+
end
|
87
|
+
@logger.info("Filtered containers: \n#{to_remove.map{|c| " + #{format_item(c)}" }.join("\n")}")
|
88
|
+
|
89
|
+
[to_remove, containers - to_remove]
|
90
|
+
end
|
91
|
+
|
92
|
+
def format_item(model)
|
93
|
+
"#{Time.at(model.created_at)} Age:#{model.age_text.ljust(10)} #{model.name.first(60).ljust(60)}"
|
94
|
+
end
|
95
|
+
|
96
|
+
def select_by_name(containers)
|
97
|
+
containers.select do |model|
|
98
|
+
@includes.any? do |pattern|
|
99
|
+
File.fnmatch(pattern, model.name)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def reject_by_name(containers)
|
105
|
+
containers.reject do |model|
|
106
|
+
@excludes.any? do |pattern|
|
107
|
+
File.fnmatch(pattern, model.name)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def select_by_deadline(containers)
|
113
|
+
containers.select do |model|
|
114
|
+
model.age > deadline
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module GitlabJanitor
|
2
|
+
class ImageCleaner
|
3
|
+
class Store
|
4
|
+
|
5
|
+
attr_reader :logger, :filename, :images
|
6
|
+
|
7
|
+
def initialize(logger:, filename: './images.txt')
|
8
|
+
@filename = filename
|
9
|
+
@logger = logger
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse_images
|
13
|
+
Docker::Image.all.map do |m|
|
14
|
+
tags = m.info.fetch('RepoTags', []) || []
|
15
|
+
tags = [m.info['id']] if tags.empty? || tags.first == '<none>:<none>'
|
16
|
+
m.info['RepoTags'] = tags
|
17
|
+
|
18
|
+
tags.map do |name|
|
19
|
+
Model.new(m, name, self)
|
20
|
+
end
|
21
|
+
end.flatten
|
22
|
+
end
|
23
|
+
|
24
|
+
def load
|
25
|
+
containers = Docker::Container.all(all: true).each_with_object({}) do |c, res|
|
26
|
+
res[c.info['ImageID']] = true
|
27
|
+
res[c.info['Image']] = true
|
28
|
+
end
|
29
|
+
|
30
|
+
File.open(@filename, 'a+') do |file|
|
31
|
+
i = 0
|
32
|
+
@images = file.readlines.select(&:present?).each_with_object({}) do |line, imgs|
|
33
|
+
i += 1
|
34
|
+
name, image_id, loaded_at = line.strip.split(' ')
|
35
|
+
loaded_at = Time.now.to_i if containers[name] || containers[image_id]
|
36
|
+
|
37
|
+
imgs[name] = { id: image_id, loaded_at: Time.at(loaded_at.to_i) }
|
38
|
+
rescue StandardError => e
|
39
|
+
logger.error "Unable to load from line #{i} '#{line}': #{e}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
@images
|
43
|
+
end
|
44
|
+
|
45
|
+
def image(img)
|
46
|
+
@images[img.name] ||= { id: img.id, loaded_at: Time.now }
|
47
|
+
end
|
48
|
+
|
49
|
+
def save(imgs = parse_images, skip_older: Time.at(0))
|
50
|
+
@images = @images.delete_if do |_k, img|
|
51
|
+
img[:loaded_at] < skip_older
|
52
|
+
end
|
53
|
+
|
54
|
+
imgs.each {|m| image(m) }
|
55
|
+
|
56
|
+
File.open(@filename, 'w+') do |file|
|
57
|
+
@images.each do |name, data|
|
58
|
+
file.puts("#{name} #{data[:id]} #{data[:loaded_at].to_i}")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'redis'
|
3
|
+
|
4
|
+
module GitlabJanitor
|
5
|
+
class ImageCleaner < BaseCleaner
|
6
|
+
|
7
|
+
class Model < BaseCleaner::Model
|
8
|
+
|
9
|
+
attr_reader :store
|
10
|
+
|
11
|
+
def initialize(model, name, store)
|
12
|
+
super(model)
|
13
|
+
@store = store
|
14
|
+
@name = name
|
15
|
+
|
16
|
+
info['_Age'] = (Time.now - Time.at(loaded_at)).round(0)
|
17
|
+
end
|
18
|
+
|
19
|
+
def loaded_at
|
20
|
+
store.image(self)[:loaded_at]
|
21
|
+
end
|
22
|
+
|
23
|
+
def name
|
24
|
+
@name || id
|
25
|
+
end
|
26
|
+
|
27
|
+
def age
|
28
|
+
info['_Age']
|
29
|
+
end
|
30
|
+
|
31
|
+
def age_text
|
32
|
+
Fugit::Duration.parse(age).deflate.to_plain_s
|
33
|
+
end
|
34
|
+
|
35
|
+
def id
|
36
|
+
info['id']
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
require_relative 'image_cleaner/store'
|
42
|
+
|
43
|
+
attr_reader :store
|
44
|
+
|
45
|
+
def initialize(image_store:, redis: nil, redis_list: 'gitlab-janitor:images_force_clean', **kwargs)
|
46
|
+
super(**kwargs)
|
47
|
+
@store = Store.new(filename: image_store, logger: logger)
|
48
|
+
@redis_url = redis
|
49
|
+
@redis_list = redis_list
|
50
|
+
end
|
51
|
+
|
52
|
+
def do_clean(remove: false)
|
53
|
+
store.load
|
54
|
+
|
55
|
+
force_clean(remove: remove)
|
56
|
+
|
57
|
+
to_remove, keep = prepare(store.parse_images)
|
58
|
+
store.save(skip_older: Time.now - @deadline)
|
59
|
+
|
60
|
+
return if to_remove.empty?
|
61
|
+
|
62
|
+
keep.each {|m| logger.debug(" KEEP #{m.name}") }
|
63
|
+
|
64
|
+
if remove
|
65
|
+
logger.info 'Removing images...'
|
66
|
+
to_remove.each do |model|
|
67
|
+
return false if exiting?
|
68
|
+
|
69
|
+
logger.tagged(model.name) do
|
70
|
+
logger.debug ' Removing...'
|
71
|
+
log_exception('Remove') { out, _status = Open3.capture2e("docker rmi #{model.name}"); logger.info(out) }
|
72
|
+
logger.debug ' Removing COMPLETED'
|
73
|
+
end
|
74
|
+
end
|
75
|
+
else
|
76
|
+
logger.info 'Skip removal due to dry run'
|
77
|
+
end
|
78
|
+
ensure
|
79
|
+
if remove
|
80
|
+
logger.info 'docker image prune -f'
|
81
|
+
out, _status = Open3.capture2e("docker image prune -f")
|
82
|
+
logger.info(out)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def prepare(images)
|
87
|
+
to_remove = images
|
88
|
+
|
89
|
+
@logger.info("Selected images: \n#{to_remove.map{|c| " + #{format_item(c)}" }.join("\n")}")
|
90
|
+
|
91
|
+
@logger.info("Filtering images by deadline: older than #{Fugit::Duration.parse(@deadline).deflate.to_plain_s}...")
|
92
|
+
to_remove = select_by_deadline(to_remove)
|
93
|
+
if to_remove.empty?
|
94
|
+
@logger.info('Noting to remove.')
|
95
|
+
return [], images
|
96
|
+
end
|
97
|
+
@logger.info("Filtered images: \n#{to_remove.map{|c| " !! #{format_item(c)}" }.join("\n")}")
|
98
|
+
|
99
|
+
[to_remove, (images - to_remove)]
|
100
|
+
end
|
101
|
+
|
102
|
+
def format_item(model)
|
103
|
+
"#{model.loaded_at} Age:#{model.age_text.ljust(13)} #{model.name.first(60).ljust(60)}"
|
104
|
+
end
|
105
|
+
|
106
|
+
def select_by_deadline(images)
|
107
|
+
images.select do |model|
|
108
|
+
model.age > deadline
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def force_clean(remove: false)
|
113
|
+
return if @redis_url.nil?
|
114
|
+
logger.info("Force clean image from #{@redis_url}/#{@redis_url}...")
|
115
|
+
|
116
|
+
redis = Redis.new(url: @redis_url)
|
117
|
+
redis.ltrim(@redis_list, 0, 10)
|
118
|
+
now = Time.now
|
119
|
+
redis.lrange(@redis_list, 0, -1).each do |pair|
|
120
|
+
image, ts = pair.split('|')
|
121
|
+
if (now - Time.at(ts.to_i)) > 10.seconds
|
122
|
+
if remove
|
123
|
+
logger.info("Force clean #{image}")
|
124
|
+
log_exception('Remove') { out, _status = Open3.capture2e("docker rmi #{image}"); logger.info(out) }
|
125
|
+
else
|
126
|
+
logger.info("Skip Force clean #{image} due to dry run")
|
127
|
+
end
|
128
|
+
else
|
129
|
+
logger.info("Delay force clean #{image} by time")
|
130
|
+
end
|
131
|
+
rescue StandardError => e
|
132
|
+
logger.error("Error from force line: '#{pair}': #{e.inspect}")
|
133
|
+
end
|
134
|
+
rescue StandardError => e
|
135
|
+
logger.error("Unable to retrieve data from redis: #{e}")
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
@@ -1,11 +1,7 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
class Fmt < ActiveSupport::Logger::SimpleFormatter
|
4
|
-
def call(severity, timestamp, progname, msg)
|
5
|
-
super
|
6
|
-
end
|
7
|
-
end
|
1
|
+
require 'logger'
|
2
|
+
require 'active_support/all'
|
8
3
|
|
4
|
+
module GitlabJanitor
|
9
5
|
class Util
|
10
6
|
|
11
7
|
TERM_SIGNALS = %w[INT TERM].freeze
|
@@ -21,27 +17,25 @@ module GitlabJanitor
|
|
21
17
|
end
|
22
18
|
|
23
19
|
def logger
|
24
|
-
$logger
|
20
|
+
$logger ||= ActiveSupport::TaggedLogging.new(Logger.new(STDOUT)).tap do |logger|
|
21
|
+
logger.level = ENV.fetch('LOG_LEVEL', Logger::INFO)
|
22
|
+
formatter = Logger::Formatter.new
|
23
|
+
formatter.extend ActiveSupport::TaggedLogging::Formatter
|
24
|
+
logger.formatter = formatter
|
25
|
+
end
|
25
26
|
end
|
26
27
|
|
27
28
|
def setup
|
28
29
|
STDOUT.sync = true
|
29
30
|
STDERR.sync = true
|
30
31
|
|
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
32
|
initialize_signal_handlers
|
40
33
|
|
41
34
|
String.class_eval do
|
42
35
|
def to_bool
|
43
36
|
return true if self == true || self =~ (/(true|t|yes|y|1)$/i)
|
44
37
|
return false if self == false || self.blank? || self =~ (/(false|f|no|n|0)$/i)
|
38
|
+
|
45
39
|
raise ArgumentError.new("invalid value for Boolean: \"#{self}\"")
|
46
40
|
end
|
47
41
|
end
|
@@ -50,10 +44,10 @@ module GitlabJanitor
|
|
50
44
|
def initialize_signal_handlers
|
51
45
|
TERM_SIGNALS.each do |sig|
|
52
46
|
trap(sig) do |*_args|
|
53
|
-
TERM_SIGNALS.each do |
|
54
|
-
trap(
|
55
|
-
|
56
|
-
Kernel
|
47
|
+
TERM_SIGNALS.each do |s|
|
48
|
+
trap(s) do |*_args|
|
49
|
+
warn 'Forcing exit!'
|
50
|
+
Kernel.exit!(1)
|
57
51
|
end
|
58
52
|
end
|
59
53
|
|
@@ -66,4 +60,5 @@ module GitlabJanitor
|
|
66
60
|
end
|
67
61
|
|
68
62
|
end
|
69
|
-
end
|
63
|
+
end
|
64
|
+
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module GitlabJanitor
|
2
|
+
class VolumeCleaner < BaseCleaner
|
3
|
+
|
4
|
+
class Model < BaseCleaner::Model
|
5
|
+
|
6
|
+
def initialize(model)
|
7
|
+
super(model)
|
8
|
+
|
9
|
+
info['_Age'] = (Time.now - Time.parse(created_at)).round(0)
|
10
|
+
end
|
11
|
+
|
12
|
+
def created_at
|
13
|
+
info['CreatedAt']
|
14
|
+
end
|
15
|
+
|
16
|
+
def name
|
17
|
+
info['Name']
|
18
|
+
end
|
19
|
+
|
20
|
+
def age
|
21
|
+
info['_Age']
|
22
|
+
end
|
23
|
+
|
24
|
+
def age_text
|
25
|
+
Fugit::Duration.parse(age).deflate.to_plain_s
|
26
|
+
end
|
27
|
+
|
28
|
+
def mountpoint
|
29
|
+
info['Mountpoint']
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_reader :includes
|
35
|
+
|
36
|
+
def initialize(includes: [''], **kwargs)
|
37
|
+
super(**kwargs)
|
38
|
+
@includes = includes
|
39
|
+
end
|
40
|
+
|
41
|
+
def do_clean(remove: false)
|
42
|
+
to_remove, keep = prepare(Docker::Volume.all.map{|m| Model.new(m) })
|
43
|
+
|
44
|
+
return if to_remove.empty?
|
45
|
+
|
46
|
+
keep.each {|m| logger.debug(" KEEP #{m.name}") }
|
47
|
+
|
48
|
+
if remove
|
49
|
+
logger.info 'Removing volumes...'
|
50
|
+
to_remove.each do |model|
|
51
|
+
return false if exiting?
|
52
|
+
|
53
|
+
logger.tagged(model.name.first(10)) do
|
54
|
+
logger.debug ' Removing...'
|
55
|
+
log_exception('Remove') { model.remove }
|
56
|
+
logger.debug ' Removing COMPLETED'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
else
|
60
|
+
logger.info 'Skip removal due to dry run'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def prepare(volumes)
|
65
|
+
@logger.debug('Selecting unnamed volumes...')
|
66
|
+
to_remove = select_unnamed(volumes)
|
67
|
+
if to_remove.empty?
|
68
|
+
@logger.info('Noting to remove.')
|
69
|
+
return [], volumes
|
70
|
+
end
|
71
|
+
@logger.info("Selected volumes: \n#{to_remove.map{|c| " + #{format_item(c)}" }.join("\n")}")
|
72
|
+
|
73
|
+
@logger.debug("Selecting volumes by includes #{@includes}...")
|
74
|
+
to_remove += select_by_name(volumes)
|
75
|
+
if to_remove.empty?
|
76
|
+
@logger.info('Noting to remove.')
|
77
|
+
return [], images
|
78
|
+
end
|
79
|
+
@logger.info("Selected volumes: \n#{to_remove.map{|c| " + #{format_item(c)}" }.join("\n")}")
|
80
|
+
|
81
|
+
@logger.info("Filtering volumes by deadline: older than #{Fugit::Duration.parse(@deadline).deflate.to_plain_s}...")
|
82
|
+
to_remove = select_by_deadline(to_remove)
|
83
|
+
if to_remove.empty?
|
84
|
+
@logger.info('Noting to remove.')
|
85
|
+
return [], volumes
|
86
|
+
end
|
87
|
+
@logger.info("Filtered volumes: \n#{to_remove.map{|c| " !! #{format_item(c)}" }.join("\n")}")
|
88
|
+
|
89
|
+
[to_remove, (volumes - to_remove)]
|
90
|
+
end
|
91
|
+
|
92
|
+
def format_item(model)
|
93
|
+
"#{Time.parse(model.created_at)} Age:#{model.age_text.ljust(13)} #{model.name.first(10).ljust(10)} #{model.mountpoint}"
|
94
|
+
end
|
95
|
+
|
96
|
+
def select_by_name(volumes)
|
97
|
+
volumes.select do |model|
|
98
|
+
@includes.any? do |pattern|
|
99
|
+
File.fnmatch(pattern, model.name)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
SHA_RX = /^[a-zA-Z0-9]{64}$/.freeze
|
105
|
+
|
106
|
+
def select_unnamed(volumes)
|
107
|
+
volumes.select do |model|
|
108
|
+
SHA_RX.match(model.name)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def select_by_deadline(volumes)
|
113
|
+
volumes.select do |model|
|
114
|
+
model.age > deadline
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
@@ -0,0 +1,8 @@
|
|
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'
|
6
|
+
require_relative 'gitlab_janitor/image_cleaner'
|
7
|
+
require_relative 'gitlab_janitor/cache_cleaner'
|
8
|
+
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gitlab-janitor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.2.92939
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samoilenko Yuri
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-08-
|
11
|
+
date: 2022-08-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: awesome_print
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: bundler
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -101,13 +115,27 @@ dependencies:
|
|
101
115
|
- !ruby/object:Gem::Version
|
102
116
|
version: '0'
|
103
117
|
- !ruby/object:Gem::Dependency
|
104
|
-
name:
|
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
|
105
133
|
requirement: !ruby/object:Gem::Requirement
|
106
134
|
requirements:
|
107
135
|
- - ">="
|
108
136
|
- !ruby/object:Gem::Version
|
109
137
|
version: '0'
|
110
|
-
type: :
|
138
|
+
type: :runtime
|
111
139
|
prerelease: false
|
112
140
|
version_requirements: !ruby/object:Gem::Requirement
|
113
141
|
requirements:
|
@@ -115,21 +143,21 @@ dependencies:
|
|
115
143
|
- !ruby/object:Gem::Version
|
116
144
|
version: '0'
|
117
145
|
- !ruby/object:Gem::Dependency
|
118
|
-
name:
|
146
|
+
name: fugit
|
119
147
|
requirement: !ruby/object:Gem::Requirement
|
120
148
|
requirements:
|
121
|
-
- - "
|
149
|
+
- - ">="
|
122
150
|
- !ruby/object:Gem::Version
|
123
|
-
version: '
|
151
|
+
version: '0'
|
124
152
|
type: :runtime
|
125
153
|
prerelease: false
|
126
154
|
version_requirements: !ruby/object:Gem::Requirement
|
127
155
|
requirements:
|
128
|
-
- - "
|
156
|
+
- - ">="
|
129
157
|
- !ruby/object:Gem::Version
|
130
|
-
version: '
|
158
|
+
version: '0'
|
131
159
|
- !ruby/object:Gem::Dependency
|
132
|
-
name:
|
160
|
+
name: redis
|
133
161
|
requirement: !ruby/object:Gem::Requirement
|
134
162
|
requirements:
|
135
163
|
- - ">="
|
@@ -143,7 +171,7 @@ dependencies:
|
|
143
171
|
- !ruby/object:Gem::Version
|
144
172
|
version: '0'
|
145
173
|
- !ruby/object:Gem::Dependency
|
146
|
-
name:
|
174
|
+
name: optparse
|
147
175
|
requirement: !ruby/object:Gem::Requirement
|
148
176
|
requirements:
|
149
177
|
- - ">="
|
@@ -157,7 +185,7 @@ dependencies:
|
|
157
185
|
- !ruby/object:Gem::Version
|
158
186
|
version: '0'
|
159
187
|
- !ruby/object:Gem::Dependency
|
160
|
-
name:
|
188
|
+
name: tzinfo-data
|
161
189
|
requirement: !ruby/object:Gem::Requirement
|
162
190
|
requirements:
|
163
191
|
- - ">="
|
@@ -188,11 +216,15 @@ files:
|
|
188
216
|
- docker-compose.yml
|
189
217
|
- gitlab-janitor.gemspec
|
190
218
|
- lib/gitlab-janitor.rb
|
191
|
-
- lib/
|
192
|
-
- lib/
|
193
|
-
- lib/
|
194
|
-
- lib/
|
195
|
-
- lib/
|
219
|
+
- lib/gitlab_janitor.rb
|
220
|
+
- lib/gitlab_janitor/base_cleaner.rb
|
221
|
+
- lib/gitlab_janitor/cache_cleaner.rb
|
222
|
+
- lib/gitlab_janitor/container_cleaner.rb
|
223
|
+
- lib/gitlab_janitor/image_cleaner.rb
|
224
|
+
- lib/gitlab_janitor/image_cleaner/store.rb
|
225
|
+
- lib/gitlab_janitor/utils.rb
|
226
|
+
- lib/gitlab_janitor/version.rb
|
227
|
+
- lib/gitlab_janitor/volume_cleaner.rb
|
196
228
|
homepage: https://github.com/RnD-Soft/gitlab-janitor
|
197
229
|
licenses:
|
198
230
|
- MIT
|
@@ -212,7 +244,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
212
244
|
- !ruby/object:Gem::Version
|
213
245
|
version: '0'
|
214
246
|
requirements: []
|
215
|
-
rubygems_version: 3.
|
247
|
+
rubygems_version: 3.1.6
|
216
248
|
signing_key:
|
217
249
|
specification_version: 4
|
218
250
|
summary: GitLab Janitor is a tool to automatically manage stalled containers when
|