-
Notifications
You must be signed in to change notification settings - Fork 96
/
container_cleaner.py
executable file
·150 lines (120 loc) · 5.48 KB
/
container_cleaner.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#!/usr/bin/python3
# (c) 2019 fvogt@suse.de
# GPLv3-only
import osc.conf
import osc.core
import logging
import ToolBase
import sys
import re
from lxml import etree as xml
class ContainerCleaner(ToolBase.ToolBase):
def __init__(self):
ToolBase.ToolBase.__init__(self)
self.logger = logging.getLogger(__name__)
def getDirEntries(self, path):
url = self.makeurl(path)
directory = xml.parse(self.retried_GET(url))
return directory.xpath("entry/@name")
def getDirBinaries(self, path):
url = self.makeurl(path)
directory = xml.parse(self.retried_GET(url))
return directory.xpath("binary/@filename")
def findSourcepkgsToDelete(self, project):
# Get a list of all images
srccontainers = self.getDirEntries(["source", project])
# Sort them into buckets for each package:
# {"opensuse-tumbleweed-image": ["opensuse-tumbleweed-image.20190402134201", ...]}
buckets = {}
regex_maintenance_release = re.compile(R"^(.+)\.[0-9]+$")
for srccontainer in srccontainers:
# Get the right bucket
match = regex_maintenance_release.match(srccontainer)
if match:
# Maintenance release
package = match.group(1)
else:
# Not renamed
package = srccontainer
if package not in buckets:
buckets[package] = []
buckets[package] += [srccontainer]
for package in buckets:
# Sort each bucket: Newest provider first
buckets[package].sort(reverse=True)
logging.debug("Found %d providers of %s", len(buckets[package]), package)
# Get a hash for sourcecontainer -> arch with binaries
# {"opensuse-tumbleweed-image.20190309164844": ["aarch64", "armv7l", "armv6l"],
# "kubic-pause-image.20190306124139": ["x86_64", "i586"], ... }
srccontainerarchs = {}
archs = self.getDirEntries(["build", project, "containers"])
regex_srccontainer = re.compile(R"^([^:]+)(:[^:]+)?$")
for arch in archs:
buildcontainers = self.getDirEntries(["build", project, "containers", arch])
for buildcontainer in buildcontainers:
bins = self.getDirBinaries(["build", project, "containers", arch, buildcontainer])
if len(bins) > 0:
match = regex_srccontainer.match(buildcontainer)
if not match:
raise Exception(f"Could not map {buildcontainer} to source container")
srccontainer = match.group(1)
if srccontainer not in srccontainers:
raise Exception(f"Mapped {buildcontainer} to wrong source container ({srccontainer})")
if srccontainer not in srccontainerarchs:
srccontainerarchs[srccontainer] = []
logging.debug("%s provides binaries for %s", srccontainer, arch)
srccontainerarchs[srccontainer] += [arch]
# Now go through each bucket and find out what doesn't contribute to the newest five
can_delete = []
for package in buckets:
# {"x86_64": 1, "aarch64": 2, ...}
archs_found = {}
for arch in archs:
archs_found[arch] = 0
for srccontainer in buckets[package]:
contributes = False
if srccontainer in srccontainerarchs:
for arch in srccontainerarchs[srccontainer]:
if archs_found[arch] < 5:
archs_found[arch] += 1
contributes = True
if contributes:
logging.debug("%s contributes to %s", srccontainer, package)
else:
logging.info("%s does not contribute", srccontainer)
if len([count for count in archs_found.values() if count > 0]) == 0:
# If there are A, B, C and D, with only C and D providing binaries,
# A and B aren't deleted because they have newer sources. This is
# to avoid deleting something due to unforeseen circumstances, e.g.
# OBS didn't copy the binaries yet.
logging.info("No newer provider found either, ignoring")
else:
can_delete += [srccontainer]
return can_delete
def run(self, project):
packages = self.findSourcepkgsToDelete(project)
for package in packages:
url = self.makeurl(["source", project, package])
if self.dryrun:
logging.info("DELETE %s", url)
else:
osc.core.http_DELETE(url)
class CommandLineInterface(ToolBase.CommandLineInterface):
def __init__(self, *args, **kwargs):
ToolBase.CommandLineInterface.__init__(self, args, kwargs)
def setup_tool(self):
tool = ContainerCleaner()
if self.options.debug:
logging.basicConfig(level=logging.DEBUG)
elif self.options.verbose:
logging.basicConfig(level=logging.INFO)
return tool
def do_run(self, subcmd, opts, project):
"""${cmd_name}: run the Container cleaner for the specified project
${cmd_usage}
${cmd_option_list}
"""
self.tool.run(project)
if __name__ == "__main__":
cli = CommandLineInterface()
sys.exit(cli.main())