From 8a5e88a4b8570511ff8266465e1676d2c4289ef3 Mon Sep 17 00:00:00 2001 From: "Owen W. Taylor" Date: Mon, 2 Oct 2023 16:38:58 -0400 Subject: [PATCH] When copying containers, make sure tags are valid OCI tags Not everything that is a valid - is a valid tag for an OCI registry. Coerce the tags that we push containers to to be valid. Signed-off-by: Owen W. Taylor --- bodhi-server/bodhi/server/util.py | 31 ++++++++++++++++++++++++++++++- bodhi-server/tests/test_util.py | 15 +++++++++++++++ news/PR5497.bug | 1 + 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 news/PR5497.bug diff --git a/bodhi-server/bodhi/server/util.py b/bodhi-server/bodhi/server/util.py index 62249f17e2..5ef57454f2 100644 --- a/bodhi-server/bodhi/server/util.py +++ b/bodhi-server/bodhi/server/util.py @@ -1161,6 +1161,34 @@ def _get_build_repository_and_digest(build): return repository, digest +def make_valid_container_tag(original_tag): + """ + Create a valid container tag from an arbitrary string. + + Turns an arbitrary string (typically a koji 'version' or 'version-release' string), + into a valid OCI registry tag. This operation isn't reversable - multiple + tags might end up as the same thing. For example, 1.0_a5', '1.0^a5', and '1.0~a5' + all end up as '1.0_a5'. + + Args: + original_tag: str: an arbitrary string + Returns: + str: a valid OCI registry tag based on original tag + """ + # https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pulling-manifests + # + # Throughout this document, as a tag MUST be at most 128 characters in length + # and MUST match the following regular expression + # + # [a-zA-Z0-9_][a-zA-Z0-9._-]{0,127} + + # Replace invalid characters with _ + new_tag = re.sub(r'^[^a-zA-Z0-9_]|[^a-zA-Z0-9._-]', '_', original_tag) + + # Truncate to 128 characters + return new_tag[0:128] + + def copy_container(build, destination_registry=None, destination_tag=None): """ Copy a ContainerBuild from the source registry to a destination registry under the given tag. @@ -1186,7 +1214,8 @@ def copy_container(build, destination_registry=None, destination_tag=None): repository, digest = _get_build_repository_and_digest(build) source_url = _container_image_url(source_registry, repository, digest=digest) - destination_url = _container_image_url(destination_registry, repository, tag=destination_tag) + destination_url = _container_image_url(destination_registry, repository, + tag=make_valid_container_tag(destination_tag)) skopeo_cmd = [ config.get('skopeo.cmd'), 'copy', source_url, destination_url] diff --git a/bodhi-server/tests/test_util.py b/bodhi-server/tests/test_util.py index edb438d9f8..819dd09b19 100644 --- a/bodhi-server/tests/test_util.py +++ b/bodhi-server/tests/test_util.py @@ -1264,6 +1264,21 @@ def test_build_evr(self): assert util.build_evr(build) == ('2', '1', '2.fc30') +class TestMakeValidContainerTag(base.BasePyTestCase): + """Test the make_valid_container_tag() function.""" + + def test_valid_tag(self): + def E(input, result): + assert util.make_valid_container_tag(input) == result + + # Invalid characters turned to _ + E("%{foo}-%{bar}", "__foo_-__bar_") + # . is only invalid when leading + E(".f-.g", "_f-.g") + # truncate to 128 characters + E("x" * 129, "x" * 128) + + @mock.patch('bodhi.server.util.cmd', autospec=True) @mock.patch('bodhi.server.util._container_image_url', new=lambda sr, r, tag=None, digest=None: diff --git a/news/PR5497.bug b/news/PR5497.bug new file mode 100644 index 0000000000..7717bfa590 --- /dev/null +++ b/news/PR5497.bug @@ -0,0 +1 @@ +Fix handling container tags which aren't valid OCI tags