2021-02-16 19:21:49 +03:00
#!/usr/bin/env python3
#
# Copyright (C) 2021 Red Hat, Inc.
# SPDX-License-Identifier: LGPL-2.1-or-later
import argparse
2021-03-12 19:55:08 +03:00
import os
2021-02-16 19:21:49 +03:00
import pathlib
2021-03-12 19:52:50 +03:00
import pty
2021-03-12 19:48:47 +03:00
import subprocess
import sys
2021-03-15 17:42:13 +03:00
import textwrap
2021-02-16 19:21:49 +03:00
2021-03-16 11:47:23 +03:00
import util
2021-02-16 19:21:49 +03:00
class Parser:
def __init__(self):
2021-03-12 19:55:08 +03:00
# Options that are common to all actions that use containers
containerparser = argparse.ArgumentParser(add_help=False)
containerparser.add_argument(
"target",
help="perform action on target OS",
)
containerparser.add_argument(
"--engine",
choices=["auto", "podman", "docker"],
default="auto",
help="container engine to use",
)
containerparser.add_argument(
"--login",
default=os.getlogin(), # exempt from syntax-check
help="login to use inside the container",
)
containerparser.add_argument(
"--image-prefix",
default="registry.gitlab.com/libvirt/libvirt/ci-",
help="use container images from non-default location",
)
containerparser.add_argument(
"--image-tag",
default=":latest",
help="use container images with non-default tags",
)
2021-03-12 20:00:52 +03:00
# Options that are common to all actions that call the
# project's build system
mesonparser = argparse.ArgumentParser(add_help=False)
mesonparser.add_argument(
"--meson-args",
default="",
help="additional arguments passed to meson "
"(eg --meson-args='-Dopt1=enabled -Dopt2=disabled')",
)
mesonparser.add_argument(
"--ninja-args",
default="",
help="additional arguments passed to ninja",
)
2021-03-16 11:47:23 +03:00
# Options that are common to actions communicating with a GitLab
# instance
gitlabparser = argparse.ArgumentParser(add_help=False)
gitlabparser.add_argument(
"--namespace",
default="libvirt/libvirt",
help="GitLab project namespace"
)
gitlabparser.add_argument(
"--gitlab-uri",
default="https://gitlab.com",
help="base GitLab URI"
)
2021-02-16 19:21:49 +03:00
# Main parser
2021-03-18 10:34:18 +03:00
self._parser = argparse.ArgumentParser()
subparsers = self._parser.add_subparsers(
2021-02-16 19:21:49 +03:00
dest="action",
metavar="ACTION",
)
subparsers.required = True
2021-03-12 20:00:52 +03:00
# build action
buildparser = subparsers.add_parser(
"build",
help="run a build in a container",
parents=[containerparser, mesonparser],
2021-03-16 17:44:06 +03:00
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
2021-03-12 20:00:52 +03:00
)
2021-03-18 10:34:18 +03:00
buildparser.set_defaults(func=Application._action_build)
2021-03-12 20:00:52 +03:00
2021-03-12 20:01:43 +03:00
# test action
testparser = subparsers.add_parser(
"test",
help="run a build in a container (including tests)",
parents=[containerparser, mesonparser],
2021-03-16 17:44:06 +03:00
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
2021-03-12 20:01:43 +03:00
)
2021-03-18 10:34:18 +03:00
testparser.set_defaults(func=Application._action_test)
2021-03-12 20:01:43 +03:00
2021-03-12 19:55:08 +03:00
# shell action
shellparser = subparsers.add_parser(
"shell",
help="start a shell in a container",
parents=[containerparser],
2021-03-16 17:44:06 +03:00
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
2021-03-12 19:55:08 +03:00
)
2021-03-18 10:34:18 +03:00
shellparser.set_defaults(func=Application._action_shell)
2021-03-12 19:55:08 +03:00
2021-03-12 19:52:50 +03:00
# list-images action
listimagesparser = subparsers.add_parser(
"list-images",
help="list known container images",
2021-03-16 11:47:23 +03:00
parents=[gitlabparser],
2021-03-16 17:44:06 +03:00
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
2021-03-12 19:52:50 +03:00
)
2021-03-18 10:34:18 +03:00
listimagesparser.set_defaults(func=Application._action_list_images)
2021-03-12 19:52:50 +03:00
2021-09-09 16:20:44 +03:00
# check_stale action
check_staleparser = subparsers.add_parser(
"check-stale",
help="check for existence of stale images on the GitLab instance",
parents=[gitlabparser],
2021-03-16 17:44:06 +03:00
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
2021-03-12 19:48:47 +03:00
)
2021-09-09 16:20:44 +03:00
check_staleparser.set_defaults(func=Application._action_check_stale)
2021-03-12 19:48:47 +03:00
2021-02-16 19:21:49 +03:00
def parse(self):
2021-03-18 10:34:18 +03:00
return self._parser.parse_args()
2021-02-16 19:21:49 +03:00
class Application:
def __init__(self):
2021-03-18 10:34:18 +03:00
self._basedir = pathlib.Path(__file__).resolve().parent
self._args = Parser().parse()
2021-02-16 19:21:49 +03:00
2021-03-18 10:34:18 +03:00
def _make_run(self, target):
2021-03-12 19:52:50 +03:00
args = [
"-C",
2021-03-18 10:34:18 +03:00
self._basedir,
2021-03-12 19:52:50 +03:00
target,
]
2021-03-18 10:34:18 +03:00
if self._args.action in ["build", "test", "shell"]:
2021-03-12 19:55:08 +03:00
args.extend([
2021-03-18 10:34:18 +03:00
f"CI_ENGINE={self._args.engine}",
f"CI_USER_LOGIN={self._args.login}",
f"CI_IMAGE_PREFIX={self._args.image_prefix}",
f"CI_IMAGE_TAG={self._args.image_tag}",
2021-03-12 19:55:08 +03:00
])
2021-03-18 10:34:18 +03:00
if self._args.action in ["build", "test"]:
2021-03-12 20:00:52 +03:00
args.extend([
2023-01-31 20:06:53 +03:00
f"MESON_ARGS={self._args.meson_args}",
f"NINJA_ARGS={self._args.ninja_args}",
2021-03-12 20:00:52 +03:00
])
2021-03-12 19:52:50 +03:00
if pty.spawn(["make"] + args) != 0:
sys.exit("error: 'make' failed")
2021-03-18 10:34:18 +03:00
def _lcitool_run(self, args):
output = subprocess.check_output([self._args.lcitool] + args)
2021-03-12 19:48:47 +03:00
return output.decode("utf-8")
2021-03-18 10:34:18 +03:00
def _check_stale_images(self):
namespace = self._args.namespace
gitlab_uri = self._args.gitlab_uri
2021-03-15 17:42:13 +03:00
registry_uri = util.get_registry_uri(namespace, gitlab_uri)
2021-09-09 16:20:44 +03:00
stale_images = util.get_registry_stale_images(registry_uri, self._basedir)
2021-03-15 17:42:13 +03:00
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: <access_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))
2021-03-18 10:34:18 +03:00
def _action_build(self):
self._make_run(f"ci-build@{self._args.target}")
2021-03-12 20:00:52 +03:00
2021-03-18 10:34:18 +03:00
def _action_test(self):
self._make_run(f"ci-test@{self._args.target}")
2021-03-12 20:01:43 +03:00
2021-03-18 10:34:18 +03:00
def _action_shell(self):
self._make_run(f"ci-shell@{self._args.target}")
2021-03-12 19:55:08 +03:00
2021-03-18 10:34:18 +03:00
def _action_list_images(self):
registry_uri = util.get_registry_uri(self._args.namespace,
self._args.gitlab_uri)
2021-03-16 11:47:23 +03:00
images = util.get_registry_images(registry_uri)
# skip the "ci-" prefix each of our container images' name has
name_prefix = "ci-"
names = [i["name"][len(name_prefix):] for i in images]
names.sort()
native = [name for name in names if "-cross-" not in name]
cross = [name for name in names if "-cross-" in name]
spacing = 4 * " "
print("Available x86 container images:\n")
print(spacing + ("\n" + spacing).join(native))
if cross:
print()
print("Available cross-compiler container images:\n")
print(spacing + ("\n" + spacing).join(cross))
2021-03-12 19:52:50 +03:00
2021-09-09 16:20:44 +03:00
def _action_check_stale(self):
self._check_stale_images()
2021-03-15 17:42:13 +03:00
2021-02-16 19:21:49 +03:00
def run(self):
2021-03-18 10:34:18 +03:00
self._args.func(self)
2021-02-16 19:21:49 +03:00
if __name__ == "__main__":
Application().run()