[libvirt PATCH 00/11] ci: helper: Improve cross-building support and more

Mainly, this switches the output of the list-images action to be more compact and changes all actions that work on containers so that the cross-building architecture is provided as a separate argument. A few additional cleanups / improvements are thrown in as well. Andrea Bolognani (11): ci: util: Drop documentation for api_version parameter ci: util: Replace get_image_distro() with get_image_info() ci: util: Document that get_registry_images() returns raw data ci: util: Add _raw suffix to get_registry_images() ci: util: Add new get_registry_images() function ci: helper: Use get_registry_images() ci: helper: Improve output for list-images action ci: helper: Add _make_run_action() function ci: helper: Accept --cross-arch argument ci: helper: Enable gitlabparser for container actions ci: helper: Perform some validation on containers ci/helper | 58 +++++++++++++++++++++++++++---------------- ci/util.py | 73 ++++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 93 insertions(+), 38 deletions(-) -- 2.26.3

It's a leftover from development. Signed-off-by: Andrea Bolognani <abologna@redhat.com> --- ci/util.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ci/util.py b/ci/util.py index 90d58454be..304d0d4af8 100644 --- a/ci/util.py +++ b/ci/util.py @@ -12,7 +12,6 @@ def get_registry_uri(namespace: str, :param namespace: GitLab project namespace, e.g. "libvirt/libvirt" :param gitlab_uri: GitLab base URI, can be a private deployment - :param api_version: GitLab REST API version number :return: URI pointing to a namespaced project's image registry """ -- 2.26.3

On Fri, Apr 23, 2021 at 05:02:58PM +0200, Andrea Bolognani wrote:
It's a leftover from development.
Signed-off-by: Andrea Bolognani <abologna@redhat.com> --- Reviewed-by: Erik Skultety <eskultet@redhat.com>

This is a more flexible function that parses the name of the container image into its components: distro name and, where applicable, target architecture for cross-building. Signed-off-by: Andrea Bolognani <abologna@redhat.com> --- ci/util.py | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/ci/util.py b/ci/util.py index 304d0d4af8..e4e0955098 100644 --- a/ci/util.py +++ b/ci/util.py @@ -39,24 +39,32 @@ def get_registry_images(uri: str) -> List[Dict]: return json.loads(r.read().decode()) -def get_image_distro(image_name: str) -> str: +def get_image_info(image_name: str) -> Dict[str, str]: """ - Extract the name of the distro in the GitLab image registry name, e.g. - ci-debian-9-cross-mipsel --> debian-9 + Extract information about the image from its name, e.g. + + "ci-debian-9-cross-mipsel" + => { "distro": "debian-9", "cross": "mipsel" } + + "ci-centos-8" + => { "distro": "centos-8", "cross": None } :param image_name: name of the GitLab registry image - :return: distro name as a string + :return: image information """ - name_prefix = "ci-" - name_suffix = "-cross-" + distro_prefix = "ci-" + cross_prefix = "-cross-" - distro = image_name[len(name_prefix):] + distro = image_name[len(distro_prefix):] - index = distro.find(name_suffix) - if index > 0: - distro = distro[:index] + cross_index = distro.find(cross_prefix) + if cross_index > 0: + cross = distro[cross_index + len(cross_prefix):] + distro = distro[:cross_index] + else: + cross = None - return distro + return {"distro": distro, "cross": cross} def get_registry_stale_images(registry_uri: str, @@ -74,7 +82,8 @@ def get_registry_stale_images(registry_uri: str, stale_images = {} for img in images: - if get_image_distro(img["name"]) not in supported_distros: + info = get_image_info(img["name"]) + if info["distro"] not in supported_distros: stale_images[img["name"]] = img["id"] return stale_images -- 2.26.3

On Fri, Apr 23, 2021 at 05:02:59PM +0200, Andrea Bolognani wrote:
This is a more flexible function that parses the name of the container image into its components: distro name and, where applicable, target architecture for cross-building.
Signed-off-by: Andrea Bolognani <abologna@redhat.com> --- Reviewed-by: Erik Skultety <eskultet@redhat.com>

The current documentation makes it sound like the function will only return a simple list containing the name of each image available on the registry, but it actually returns a lot more information than that. Signed-off-by: Andrea Bolognani <abologna@redhat.com> --- ci/util.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ci/util.py b/ci/util.py index e4e0955098..625428f339 100644 --- a/ci/util.py +++ b/ci/util.py @@ -29,8 +29,13 @@ def get_registry_images(uri: str) -> List[Dict]: List all container images that are currently available in the given GitLab project. + The raw data, as obtained from the GitLab API, is returned: this + will contain not only the name of each image, but also additional + information such as the image ID, which can be used to perform + further operations with subsequent calls to the GitLab API. + :param uri: URI pointing to a GitLab instance's image registry - :return: list of container image names + :return: detailed information about container images """ r = urllib.request.urlopen(uri + "?per_page=100") -- 2.26.3

On Fri, Apr 23, 2021 at 05:03:00PM +0200, Andrea Bolognani wrote:
The current documentation makes it sound like the function will only return a simple list containing the name of each image available on the registry, but it actually returns a lot more information than that.
Signed-off-by: Andrea Bolognani <abologna@redhat.com> --- Reviewed-by: Erik Skultety <eskultet@redhat.com>

This stresses the fact that the function returns the data obtained from the GitLab API in its raw form; later on, we're going to implement a more convenient wrapper that extracts the information we're interested in and processes it so that it can be used more conveniently. Signed-off-by: Andrea Bolognani <abologna@redhat.com> --- ci/helper | 2 +- ci/util.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ci/helper b/ci/helper index 2c3a9f8db4..eb037d3495 100755 --- a/ci/helper +++ b/ci/helper @@ -309,7 +309,7 @@ class Application: def _action_list_images(self): registry_uri = util.get_registry_uri(self._args.namespace, self._args.gitlab_uri) - images = util.get_registry_images(registry_uri) + images = util.get_registry_images_raw(registry_uri) # skip the "ci-" prefix each of our container images' name has name_prefix = "ci-" diff --git a/ci/util.py b/ci/util.py index 625428f339..b6a99ce9c0 100644 --- a/ci/util.py +++ b/ci/util.py @@ -24,7 +24,7 @@ def get_registry_uri(namespace: str, return uri -def get_registry_images(uri: str) -> List[Dict]: +def get_registry_images_raw(uri: str) -> List[Dict]: """ List all container images that are currently available in the given GitLab project. @@ -83,7 +83,7 @@ def get_registry_stale_images(registry_uri: str, :return: dictionary formatted as: {<gitlab_image_name>: <gitlab_image_id>} """ - images = get_registry_images(registry_uri) + images = get_registry_images_raw(registry_uri) stale_images = {} for img in images: -- 2.26.3

Signed-off-by: Andrea Bolognani <abologna@redhat.com> --- ci/util.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/ci/util.py b/ci/util.py index b6a99ce9c0..b314ebd078 100644 --- a/ci/util.py +++ b/ci/util.py @@ -72,6 +72,34 @@ def get_image_info(image_name: str) -> Dict[str, str]: return {"distro": distro, "cross": cross} +def get_registry_images(registry_uri: str) -> Dict[str, List[str]]: + """ + List all container images that are currently available in the + given GitLab project. + + The returned dictionary contains an entry for each available + distro, with its associated value being a (possibly empty) list + of cross-building architectures it supports. + + :param uri: URI pointing to a GitLab instance's image registry + :return: information about container images + """ + + registry_images = get_registry_images_raw(registry_uri) + + # Parse the names of all images found on the registry and + # organize them by distro + images = {} + for image in registry_images: + info = get_image_info(image["name"]) + if info["distro"] not in images: + images[info["distro"]] = [] + if info["cross"]: + images[info["distro"]].append(info["cross"]) + + return images + + def get_registry_stale_images(registry_uri: str, supported_distros: List[str]) -> Dict[str, int]: """ -- 2.26.3

Generate output using the processed data instead of the raw data. The output remains identical for now, so this doesn't look like a big improvement, but we're soon going to change it in a way that highlights the convenience of using processed data. Signed-off-by: Andrea Bolognani <abologna@redhat.com> --- ci/helper | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/ci/helper b/ci/helper index eb037d3495..c1e1a53d51 100755 --- a/ci/helper +++ b/ci/helper @@ -309,15 +309,18 @@ class Application: def _action_list_images(self): registry_uri = util.get_registry_uri(self._args.namespace, self._args.gitlab_uri) - images = util.get_registry_images_raw(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] + images = util.get_registry_images(registry_uri) + + native = [] + cross = [] + for image_distro in images.keys(): + if images[image_distro]: + for image_cross in images[image_distro]: + cross.append(image_distro + "-cross-" + image_cross) + else: + native.append(image_distro) + native.sort() + cross.sort() spacing = 4 * " " print("Available x86 container images:\n") -- 2.26.3

This makes the output more compact by grouping together all images that are built on the same base OS. Later on, when we change the actions that operate on container images to accept an lcitool-style --cross-arch argument instead of expecting the name of the image to have the cross-building architecture baked in, this will allow users to quickly copy-and-paste the necessary information. Signed-off-by: Andrea Bolognani <abologna@redhat.com> --- ci/helper | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/ci/helper b/ci/helper index c1e1a53d51..6543a47131 100755 --- a/ci/helper +++ b/ci/helper @@ -311,25 +311,12 @@ class Application: self._args.gitlab_uri) images = util.get_registry_images(registry_uri) - native = [] - cross = [] - for image_distro in images.keys(): - if images[image_distro]: - for image_cross in images[image_distro]: - cross.append(image_distro + "-cross-" + image_cross) + for distro in sorted(images.keys()): + if images[distro]: + cross = " ".join(sorted(images[distro])) + print(f"{distro} (cross: {cross})") else: - native.append(image_distro) - native.sort() - cross.sort() - - 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)) + print(distro) def _action_refresh(self): self._refresh_containers() -- 2.26.3

On Fri, Apr 23, 2021 at 05:03:04PM +0200, Andrea Bolognani wrote:
This makes the output more compact by grouping together all images that are built on the same base OS.
Yes, it definitely does make the output more compact, but I'm still not completely sold on the idea that this is an actual improvement...
Later on, when we change the actions that operate on container images to accept an lcitool-style --cross-arch argument instead of expecting the name of the image to have the cross-building architecture baked in, this will allow users to quickly copy-and-paste the necessary information.
...you can quickly copy-and-paste the image name even now and I would argue that it's even quicker than after this series: ./helper list-images ./helper <action> <copy-paste image name> vs ./helper list-images ./helper <action> <copy-paste image name> --cross-arch <copy-paste cross arch> Right now you wouldn't even need the verification code introduced in patch 12 since you have to paste the image verbatim which IMO for this utility helper that only serves a very specific use case of a single repo is "good enough". I guess the idea is to eventually integrate this helper somehow to lcitool, so I'm wondering what is it we're trying to solve here. As for the output itself, if we want to change it, then I'd be probably more inclined towards something like virt-builder --list. There's massive information redundancy in there - no need to argue - but because the output is formatted like a simple table, tools like grep,sort,uniq and cut make the customization of the output for the average linux user an absolute no-brainer and they always get information they were looking for easily with almost no added effort. Erik

On Mon, Apr 26, 2021 at 11:59:19AM +0200, Erik Skultety wrote:
On Fri, Apr 23, 2021 at 05:03:04PM +0200, Andrea Bolognani wrote:
Later on, when we change the actions that operate on container images to accept an lcitool-style --cross-arch argument instead of expecting the name of the image to have the cross-building architecture baked in, this will allow users to quickly copy-and-paste the necessary information.
...you can quickly copy-and-paste the image name even now and I would argue that it's even quicker than after this series:
./helper list-images ./helper <action> <copy-paste image name> vs
./helper list-images ./helper <action> <copy-paste image name> --cross-arch <copy-paste cross arch>
Right now you wouldn't even need the verification code introduced in patch 12 since you have to paste the image verbatim which IMO for this utility helper that only serves a very specific use case of a single repo is "good enough".
That assumes the target name only comes from copying and pasting from the output of the list-images action... Right now, if you pass some invalid / obsolete value you get $ ./ci/helper build ubuntu-1604 make: Entering directory '/home/abologna/src/upstream/libvirt/ci' make -C /home/abologna/src/upstream/libvirt/ci ci-run-command@ubuntu-1604 CI_COMMAND="/home/abologna/build" make[1]: Entering directory '/home/abologna/src/upstream/libvirt/ci' Checking if podman is available...yes Cloning /home/abologna/src/upstream/libvirt to /home/abologna/src/upstream/libvirt/ci/scratch/src Cloning /home/abologna/src/upstream/libvirt/src/keycodemapdb to /home/abologna/src/upstream/libvirt/ci/scratch/src/src/keycodemapdb podman run \ --rm --interactive --tty --user "1000":"1000" --workdir "/home/abologna" --env CI_CONT_SRCDIR="/home/abologna/libvirt" --env CI_MESON_ARGS="" --env CI_NINJA_ARGS="" --uidmap 0:1:1000 --uidmap 1000:0:1 --uidmap 1001:1001:64536 --gidmap 0:1:1000 --gidmap 1000:0:1 --gidmap 1001:1001:64536 --volume /home/abologna/src/upstream/libvirt/ci/scratch/group:/etc/group:ro,z --volume /home/abologna/src/upstream/libvirt/ci/scratch/passwd:/etc/passwd:ro,z --volume /home/abologna/src/upstream/libvirt/ci/scratch/home:/home/abologna:z --volume /home/abologna/src/upstream/libvirt/ci/scratch/build:/home/abologna/build:z --volume /home/abologna/src/upstream/libvirt/ci/scratch/src:/home/abologna/libvirt:z --ulimit nofile=1024:1024 --cap-add=SYS_PTRACE \ registry.gitlab.com/libvirt/libvirt/ci-ubuntu-1604:latest \ /home/abologna/build Trying to pull registry.gitlab.com/libvirt/libvirt/ci-ubuntu-1604:latest... manifest unknown: manifest unknown Error: Error initializing source docker://registry.gitlab.com/libvirt/libvirt/ci-ubuntu-1604:latest: Error reading manifest latest in registry.gitlab.com/libvirt/libvirt/ci-ubuntu-1604: manifest unknown: manifest unknown make[1]: *** [Makefile:199: ci-run-command@ubuntu-1604] Error 125 make[1]: Leaving directory '/home/abologna/src/upstream/libvirt/ci' make: *** [Makefile:209: ci-build@ubuntu-1604] Error 2 make: Leaving directory '/home/abologna/src/upstream/libvirt/ci' error: 'make' failed With my patches, the output is a much more reasonable $ ./ci/helper build ubuntu-1604 Unknown target 'ubuntu-1604' I agree that having to input the target OS and the target architecture separately is slightly more copy-and-paste work, but really those are two semantically separate bits of information and it's just wrong for the user to provide a single argument that encodes both. It was an acceptable trade-off when we were using make, but now that we have an actual command-line parser at our disposal we should take advantage of it.
I guess the idea is to eventually integrate this helper somehow to lcitool, so I'm wondering what is it we're trying to solve here. As for the output itself, if we want to change it, then I'd be probably more inclined towards something like virt-builder --list. There's massive information redundancy in there - no need to argue - but because the output is formatted like a simple table, tools like grep,sort,uniq and cut make the customization of the output for the average linux user an absolute no-brainer and they always get information they were looking for easily with almost no added effort.
Changing the output to be similar to that of 'virt-builder --list' sounds good to me.

This provides a single place where we can process self._args before handing the control over to make; later on, we're going to implement more logic that can be shared by the build, test and shell actions. Signed-off-by: Andrea Bolognani <abologna@redhat.com> --- ci/helper | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/ci/helper b/ci/helper index 6543a47131..31e05c0e38 100755 --- a/ci/helper +++ b/ci/helper @@ -186,6 +186,11 @@ class Application: if pty.spawn(["make"] + args) != 0: sys.exit("error: 'make' failed") + def _make_run_action(self, action): + target = self._args.target + make_target = f"{action}@{target}" + self._make_run(make_target) + def _lcitool_run(self, args): output = subprocess.check_output([self._args.lcitool] + args) return output.decode("utf-8") @@ -298,13 +303,13 @@ class Application: print(msg.replace("STALE_DETAILS", stale_details)) def _action_build(self): - self._make_run(f"ci-build@{self._args.target}") + self._make_run_action("ci-build") def _action_test(self): - self._make_run(f"ci-test@{self._args.target}") + self._make_run_action("ci-test") def _action_shell(self): - self._make_run(f"ci-shell@{self._args.target}") + self._make_run_action("ci-shell") def _action_list_images(self): registry_uri = util.get_registry_uri(self._args.namespace, -- 2.26.3

On Fri, Apr 23, 2021 at 05:03:05PM +0200, Andrea Bolognani wrote:
This provides a single place where we can process self._args before handing the control over to make; later on, we're going to implement more logic that can be shared by the build, test and shell actions.
Signed-off-by: Andrea Bolognani <abologna@redhat.com> --- Reviewed-by: Erik Skultety <eskultet@redhat.com>

This argument is accepted by all actions that use containers. Signed-off-by: Andrea Bolognani <abologna@redhat.com> --- ci/helper | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/ci/helper b/ci/helper index 31e05c0e38..e39934d357 100755 --- a/ci/helper +++ b/ci/helper @@ -23,6 +23,10 @@ class Parser: "target", help="perform action on target OS", ) + containerparser.add_argument( + "-x", "--cross-arch", + help="target architecture for cross-compiler", + ) containerparser.add_argument( "--engine", choices=["auto", "podman", "docker"], @@ -188,7 +192,13 @@ class Application: def _make_run_action(self, action): target = self._args.target - make_target = f"{action}@{target}" + cross = self._args.cross_arch + + if cross: + make_target = f"{action}@{target}-cross-{cross}" + else: + make_target = f"{action}@{target}" + self._make_run(make_target) def _lcitool_run(self, args): -- 2.26.3

Unfortunately this doesn't work completely yet, because the underlying Makefile has no notion of the --namespace and --gitlab-uri arguments, but we need the subparser to be enabled so that we can use the corresponding information in _make_run_action() for validation purposes. Once we've reimplemented the Makefile part in Python, then the new arguments will start working as expected. Signed-off-by: Andrea Bolognani <abologna@redhat.com> --- ci/helper | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ci/helper b/ci/helper index e39934d357..cd3069a89a 100755 --- a/ci/helper +++ b/ci/helper @@ -99,7 +99,7 @@ class Parser: buildparser = subparsers.add_parser( "build", help="run a build in a container", - parents=[containerparser, mesonparser], + parents=[gitlabparser, containerparser, mesonparser], formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) buildparser.set_defaults(func=Application._action_build) @@ -108,7 +108,7 @@ class Parser: testparser = subparsers.add_parser( "test", help="run a build in a container (including tests)", - parents=[containerparser, mesonparser], + parents=[gitlabparser, containerparser, mesonparser], formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) testparser.set_defaults(func=Application._action_test) @@ -117,7 +117,7 @@ class Parser: shellparser = subparsers.add_parser( "shell", help="start a shell in a container", - parents=[containerparser], + parents=[gitlabparser, containerparser], formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) shellparser.set_defaults(func=Application._action_shell) -- 2.26.3

This reports a nice error messages when the user attempts to perform a cross-build on a target that doesn't support it, or to use a target that doesn't exist at all. Signed-off-by: Andrea Bolognani <abologna@redhat.com> --- ci/helper | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ci/helper b/ci/helper index cd3069a89a..d5bdb15112 100755 --- a/ci/helper +++ b/ci/helper @@ -191,10 +191,19 @@ class Application: sys.exit("error: 'make' failed") def _make_run_action(self, action): + registry_uri = util.get_registry_uri(self._args.namespace, + self._args.gitlab_uri) + images = util.get_registry_images(registry_uri) target = self._args.target cross = self._args.cross_arch + if target not in images: + sys.exit(f"Unknown target '{target}'") + if cross: + if cross not in images[target]: + sys.exit(f"Target '{target}' doesn't support cross '{cross}'") + make_target = f"{action}@{target}-cross-{cross}" else: make_target = f"{action}@{target}" -- 2.26.3
participants (2)
-
Andrea Bolognani
-
Erik Skultety