diff --git a/ci/helper b/ci/helper index 31cf72fbdf..65abd68e3c 100755 --- a/ci/helper +++ b/ci/helper @@ -10,6 +10,7 @@ import pty import shutil import subprocess import sys +import textwrap import util @@ -130,7 +131,7 @@ class Parser: refreshparser = subparsers.add_parser( "refresh", help="refresh data generated with lcitool", - parents=[lcitoolparser], + parents=[lcitoolparser, gitlabparser], formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) refreshparser.add_argument( @@ -139,6 +140,13 @@ class Parser: default=False, help="refresh data silently" ) + refreshparser.add_argument( + "--check-stale", + action="store", + choices=["yes", "no"], + default="yes", + help="check for existence of stale images on the GitLab instance" + ) refreshparser.set_defaults(func=Application.action_refresh) def parse(self): @@ -256,6 +264,39 @@ class Application: self.generate_vars(host) + def check_stale_images(self): + namespace = self.args.namespace + gitlab_uri = self.args.gitlab_uri + registry_uri = util.get_registry_uri(namespace, gitlab_uri) + lcitool_hosts = self.lcitool_get_hosts() + + stale_images = util.get_registry_stale_images(registry_uri, + lcitool_hosts) + if stale_images: + spacing = "\n" + 4 * " " + stale_fmt = [f"{k} (ID: {v})" for k, v in stale_images.items()] + stale_details = spacing.join(stale_fmt) + stale_ids = ' '.join([str(id) for id in stale_images.values()]) + registry_uri = util.get_registry_uri(namespace, gitlab_uri) + + msg = textwrap.dedent(f""" + The following images are stale and can be purged from the registry: + + STALE_DETAILS + + You can delete the images listed above using this shell snippet: + + $ for image_id in {stale_ids}; do + curl --request DELETE --header "PRIVATE-TOKEN: " \\ + {registry_uri}/$image_id; + done + + You can generate a personal access token here: + + {gitlab_uri}/-/profile/personal_access_tokens + """) + print(msg.replace("STALE_DETAILS", stale_details)) + def action_build(self): self.make_run(f"ci-build@{self.args.target}") @@ -291,6 +332,9 @@ class Application: self.refresh_containers() self.refresh_cirrus() + if self.args.check_stale == "yes" and not self.args.quiet: + self.check_stale_images() + def run(self): self.args.func(self) diff --git a/ci/util.py b/ci/util.py index f9f8320276..90d58454be 100644 --- a/ci/util.py +++ b/ci/util.py @@ -38,3 +38,44 @@ def get_registry_images(uri: str) -> List[Dict]: # read the HTTP response and load the JSON part of it return json.loads(r.read().decode()) + + +def get_image_distro(image_name: str) -> str: + """ + Extract the name of the distro in the GitLab image registry name, e.g. + ci-debian-9-cross-mipsel --> debian-9 + + :param image_name: name of the GitLab registry image + :return: distro name as a string + """ + name_prefix = "ci-" + name_suffix = "-cross-" + + distro = image_name[len(name_prefix):] + + index = distro.find(name_suffix) + if index > 0: + distro = distro[:index] + + return distro + + +def get_registry_stale_images(registry_uri: str, + supported_distros: List[str]) -> Dict[str, int]: + """ + Check the GitLab image registry for images that we no longer support and + which should be deleted. + + :param uri: URI pointing to a GitLab instance's image registry + :param supported_distros: list of hosts supported by lcitool + :return: dictionary formatted as: {: } + """ + + images = get_registry_images(registry_uri) + + stale_images = {} + for img in images: + if get_image_distro(img["name"]) not in supported_distros: + stale_images[img["name"]] = img["id"] + + return stale_images