|
@@ -1,28 +1,26 @@
|
1
|
1
|
#!/usr/bin/env python3
|
2
|
|
-#
|
3
|
|
-# This script purges untagged repositories and runs the garbage collector in Docker Registry >= 2.4.0.
|
4
|
|
-# It works on the whole registry or the specified repositories.
|
5
|
|
-# The optional -x flag may be used to completely remove the specified repositories or tagged images.
|
6
|
|
-#
|
7
|
|
-# NOTES:
|
8
|
|
-# - This script stops the Registry container during cleanup to prevent corruption,
|
9
|
|
-# making it temporarily unavailable to clients.
|
10
|
|
-# - This script assumes local storage (the filesystem storage driver).
|
11
|
|
-# - This script may run stand-alone (on local setups) or dockerized (which supports remote Docker setups).
|
12
|
|
-# - This script is Python 3 only.
|
13
|
|
-#
|
14
|
|
-# v1.2.1 by Ricardo Branco
|
15
|
|
-#
|
16
|
|
-# MIT License
|
17
|
|
-#
|
|
2
|
+"""
|
|
3
|
+This script purges untagged repositories and runs the garbage collector in Docker Registry >= 2.4.0.
|
|
4
|
+It works on the whole registry or the specified repositories.
|
|
5
|
+The optional -x flag may be used to completely remove the specified repositories or tagged images.
|
|
6
|
+
|
|
7
|
+NOTES:
|
|
8
|
+ - This script stops the Registry container during cleanup to prevent corruption,
|
|
9
|
+ making it temporarily unavailable to clients.
|
|
10
|
+ - This script assumes local storage (the filesystem storage driver).
|
|
11
|
+ - This script may run stand-alone (on local setups) or dockerized (which supports remote Docker setups).
|
|
12
|
+ - This script is Python 3 only.
|
|
13
|
+
|
|
14
|
+v1.2.1 by Ricardo Branco
|
|
15
|
+
|
|
16
|
+MIT License
|
|
17
|
+"""
|
18
|
18
|
|
19
|
19
|
import os
|
20
|
20
|
import re
|
21
|
21
|
import sys
|
22
|
22
|
import tarfile
|
23
|
23
|
import subprocess
|
24
|
|
-import yaml
|
25
|
|
-import docker
|
26
|
24
|
|
27
|
25
|
from argparse import ArgumentParser
|
28
|
26
|
from distutils.version import LooseVersion
|
|
@@ -30,8 +28,12 @@ from glob import iglob
|
30
|
28
|
from io import BytesIO
|
31
|
29
|
from shutil import rmtree
|
32
|
30
|
from requests import exceptions
|
|
31
|
+
|
|
32
|
+import docker
|
33
|
33
|
from docker.errors import APIError, NotFound, TLSParameterError
|
34
|
34
|
|
|
35
|
+import yaml
|
|
36
|
+
|
35
|
37
|
VERSION = "1.2.1"
|
36
|
38
|
REGISTRY_DIR = "REGISTRY_STORAGE_FILESYSTEM_ROOTREGISTRY_DIR"
|
37
|
39
|
|
|
@@ -41,10 +43,10 @@ def dockerized():
|
41
|
43
|
return os.path.isfile("/.dockerenv")
|
42
|
44
|
|
43
|
45
|
|
44
|
|
-def error(msg, Exit=True):
|
|
46
|
+def error(msg, bye=True):
|
45
|
47
|
'''Prints an error message and optionally exit with a status code of 1'''
|
46
|
48
|
print("ERROR: " + str(msg), file=sys.stderr)
|
47
|
|
- if Exit:
|
|
49
|
+ if bye:
|
48
|
50
|
sys.exit(1)
|
49
|
51
|
|
50
|
52
|
|
|
@@ -68,13 +70,13 @@ def clean_tag(repo, tag):
|
68
|
70
|
'''Clean a specific repo:tag'''
|
69
|
71
|
link = repo + "/_manifests/tags/" + tag + "/current/link"
|
70
|
72
|
if not os.path.isfile(link):
|
71
|
|
- error("No such tag: %s in repository %s" % (tag, repo), Exit=False)
|
|
73
|
+ error("No such tag: %s in repository %s" % (tag, repo), bye=False)
|
72
|
74
|
return False
|
73
|
75
|
if args.remove:
|
74
|
76
|
remove(repo + "/_manifests/tags/" + tag)
|
75
|
77
|
else:
|
76
|
|
- with open(link) as f:
|
77
|
|
- current = f.read()[len("sha256:"):]
|
|
78
|
+ with open(link) as infile:
|
|
79
|
+ current = infile.read()[len("sha256:"):]
|
78
|
80
|
path = repo + "/_manifests/tags/" + tag + "/index/sha256/"
|
79
|
81
|
for index in os.listdir(path):
|
80
|
82
|
if index == current:
|
|
@@ -89,7 +91,7 @@ def clean_repo(image):
|
89
|
91
|
repo, tag = image.split(":", 1) if ":" in image else (image, "")
|
90
|
92
|
|
91
|
93
|
if not os.path.isdir(repo):
|
92
|
|
- error("No such repository: " + repo, Exit=False)
|
|
94
|
+ error("No such repository: " + repo, bye=False)
|
93
|
95
|
return False
|
94
|
96
|
|
95
|
97
|
if args.remove:
|
|
@@ -103,8 +105,8 @@ def clean_repo(image):
|
103
|
105
|
|
104
|
106
|
currents = set()
|
105
|
107
|
for link in iglob(repo + "/_manifests/tags/*/current/link"):
|
106
|
|
- with open(link) as f:
|
107
|
|
- currents.add(f.read()[len("sha256:"):])
|
|
108
|
+ with open(link) as infile:
|
|
109
|
+ currents.add(infile.read()[len("sha256:"):])
|
108
|
110
|
for index in iglob(repo + "/_manifests/tags/*/index/sha256/*"):
|
109
|
111
|
if os.path.basename(index) not in currents:
|
110
|
112
|
remove(index)
|
|
@@ -185,27 +187,27 @@ class RegistryCleaner():
|
185
|
187
|
|
186
|
188
|
images = args.images if args.images else map(os.path.dirname, iglob("**/_manifests", recursive=True))
|
187
|
189
|
|
188
|
|
- rc = 0
|
|
190
|
+ exit_status = 0
|
189
|
191
|
for image in images:
|
190
|
192
|
if not clean_repo(image):
|
191
|
|
- rc = 1
|
|
193
|
+ exit_status = 1
|
192
|
194
|
|
193
|
195
|
if not self.garbage_collect():
|
194
|
|
- rc = 1
|
|
196
|
+ exit_status = 1
|
195
|
197
|
|
196
|
198
|
if self.container is not None:
|
197
|
199
|
self.docker.api.start(self.container)
|
198
|
|
- return rc
|
|
200
|
+ return exit_status
|
199
|
201
|
|
200
|
|
- def get_file(self, filename):
|
|
202
|
+ def get_file(self, path):
|
201
|
203
|
'''Returns the contents of the specified file from the container'''
|
202
|
204
|
try:
|
203
|
|
- archive = self.docker.api.get_archive(self.container, filename)[0]
|
204
|
|
- binary = b"".join(chunk for chunk in archive)
|
205
|
|
- with BytesIO(binary) as buf:
|
|
205
|
+ with BytesIO(
|
|
206
|
+ b"".join(_ for _ in self.docker.api.get_archive(self.container, path)[0])
|
|
207
|
+ ) as buf:
|
206
|
208
|
with tarfile.open(fileobj=buf) as tar:
|
207
|
|
- with tar.extractfile(os.path.basename(filename)) as f:
|
208
|
|
- data = f.read()
|
|
209
|
+ with tar.extractfile(os.path.basename(path)) as infile:
|
|
210
|
+ data = infile.read()
|
209
|
211
|
except NotFound as err:
|
210
|
212
|
error(err)
|
211
|
213
|
return data
|
|
@@ -302,11 +304,11 @@ Options:
|
302
|
304
|
error("The -x option requires that you specify at least one repository...")
|
303
|
305
|
|
304
|
306
|
if args.volume:
|
305
|
|
- rc = RegistryCleaner(volume=args.container_or_volume)
|
|
307
|
+ cleaner = RegistryCleaner(volume=args.container_or_volume)
|
306
|
308
|
else:
|
307
|
|
- rc = RegistryCleaner(container=args.container_or_volume)
|
|
309
|
+ cleaner = RegistryCleaner(container=args.container_or_volume)
|
308
|
310
|
|
309
|
|
- sys.exit(rc())
|
|
311
|
+ sys.exit(cleaner())
|
310
|
312
|
|
311
|
313
|
if __name__ == "__main__":
|
312
|
314
|
try:
|