-
Notifications
You must be signed in to change notification settings - Fork 309
suite: Check for existence of container images #2104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| import functools | ||
| import logging | ||
| import re | ||
| import requests | ||
|
|
||
| log = logging.getLogger(__name__) | ||
|
|
||
| # Our container images use a certain base image and flavor by default; those | ||
| # values are reflected below. If different values are used, they are appended | ||
| # to the image name. | ||
| DEFAULT_CONTAINER_BASE = 'centos:9' | ||
| DEFAULT_CONTAINER_FLAVOR = 'default' | ||
| DEFAULT_CONTAINER_IMAGE='quay.ceph.io/ceph-ci/ceph:{sha1}' | ||
| CONTAINER_REGEXP = re.compile( | ||
| r"((?P<domain>[a-zA-Z0-9._-]+)/)?((?P<org>[a-zA-Z0-9_-]+)/)?((?P<image>[a-zA-Z0-9_-]+))?(:(?P<tag>[a-zA-Z0-9._-]+))?" | ||
| ) | ||
|
|
||
|
|
||
| def resolve_container_image(image: str): | ||
| """ | ||
| Given an image locator that is potentially incomplete, construct a qualified version. | ||
|
|
||
| ':tag' -> 'quay.ceph.io/ceph-ci/ceph:tag' | ||
| 'image:tag' -> 'quay.ceph.io/ceph-ci/image:tag' | ||
| 'org/image:tag' -> 'quay.ceph.io/org/image:tag' | ||
| 'example.com/org/image:tag' -> 'example.com/org/image:tag' | ||
| """ | ||
| try: | ||
| (image_long, tag) = image.split(':') | ||
| except ValueError: | ||
| raise ValueError(f"Container image spec missing tag: {image}") from None | ||
| domain = 'quay.ceph.io' | ||
| org = 'ceph-ci' | ||
| image = 'ceph' | ||
| image_split = image_long.split('/') | ||
| assert len(image_split) <= 3 | ||
| match len(image_split): | ||
| case 3: | ||
| (domain, org, image) = image_split | ||
| case 2: | ||
| (org, image) = image_split | ||
| case _: | ||
| if image_split[0]: | ||
| image = image_split[0] | ||
| return f"{domain}/{org}/{image}:{tag}" | ||
|
|
||
|
|
||
| @functools.lru_cache() | ||
| def container_image_exists(image: str): | ||
| """ | ||
| Use the Quay API to check for the existence of a container image. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this code work for any container registry api? If so, the description is better to change.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should it? I'm not sure that is the case, but if you have a reference I would love to see it
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought this method supposed to be working with other registry as well as ceph itself. For example, downstream teuthology deploys fine cephadm using third party container registry, but if we merge this quay only specific check it might break this. So, we either support some universal api or we need to check if we actually can check the existence of the container.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Which downstream product an which non-quay registry? I can tweak this so that it skips the check if, for example, the host portion of the image locator doesn't contain 'quay', and then interested parties can contribute checks using APIs they need to query
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For example, registry.opensuse.org, or any custom goharbor.io instances, ghcr, gitlab etc. |
||
| Only tested with Quay registries. | ||
| """ | ||
| match = re.match(CONTAINER_REGEXP, image) | ||
| assert match | ||
| obj = match.groupdict() | ||
| url = f"https://{obj['domain']}/api/v1/repository/{obj['org']}/{obj['image']}/tag?filter_tag_name=eq:{obj['tag']}" | ||
| log.info(f"Checking for container existence at: {url}") | ||
| resp = requests.get(url) | ||
| return resp.ok and len(resp.json().get('tags')) >= 1 | ||
|
|
||
|
|
||
| def container_image_for_hash(hash: str, flavor='default', base_image='centos:9'): | ||
| """ | ||
| Given a sha1 and optionally a base image and flavor, attempt to return a container image locator. | ||
| """ | ||
| tag = hash | ||
| if base_image != DEFAULT_CONTAINER_BASE: | ||
| tag = f"{tag}-{base_image.replace(':', '-')}" | ||
| if flavor != DEFAULT_CONTAINER_FLAVOR: | ||
| tag = f"{tag}-{flavor}" | ||
| image_spec = resolve_container_image(f":{tag}") | ||
| if container_image_exists(image_spec): | ||
| return image_spec | ||
| else: | ||
| log.error(f"Container image not found for hash '{hash}'") | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| import pytest | ||
|
|
||
| from unittest.mock import patch | ||
|
|
||
| from teuthology.util import containers | ||
|
|
||
| @pytest.mark.parametrize( | ||
| 'input, expected', | ||
| [ | ||
| (':hash', 'quay.ceph.io/ceph-ci/ceph:hash'), | ||
| ('image:hash', 'quay.ceph.io/ceph-ci/image:hash'), | ||
| ('org/image:hash', 'quay.ceph.io/org/image:hash'), | ||
| ('example.com/org/image:hash', 'example.com/org/image:hash'), | ||
| ('image', ValueError), | ||
| ('org/image', ValueError), | ||
| ('domain.net/org/image', ValueError), | ||
| ] | ||
| ) | ||
| def test_resolve_container_image(input, expected): | ||
| if isinstance(expected, str): | ||
| assert expected == containers.resolve_container_image(input) | ||
| else: | ||
| with pytest.raises(expected): | ||
| containers.resolve_container_image(input) | ||
|
|
||
| @pytest.mark.parametrize( | ||
| 'image, url', | ||
| [ | ||
| ('example.com/org/image:tag', 'https://example.com/api/v1/repository/org/image/tag?filter_tag_name=eq:tag'), | ||
| ] | ||
| ) | ||
| def test_container_image_exists(image, url): | ||
| with patch("teuthology.util.containers.requests.get") as m_get: | ||
| containers.container_image_exists(image) | ||
| m_get.assert_called_once_with(url) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize( | ||
| 'hash, flavor, base_image, rci_input', | ||
| [ | ||
| ('hash', 'flavor', 'base-image', ':hash-base-image-flavor'), | ||
| ('hash', 'default', 'centos:9', ':hash'), | ||
| ('hash', 'default', 'rockylinux-10', ':hash-rockylinux-10'), | ||
| ] | ||
| ) | ||
| def test_container_image_for_hash(hash, flavor, base_image, rci_input): | ||
| with patch('teuthology.util.containers.resolve_container_image') as m_rci: | ||
| with patch('teuthology.util.containers.container_image_exists'): | ||
| containers.container_image_for_hash(hash, flavor, base_image) | ||
| m_rci.assert_called_once_with(rci_input) |
Uh oh!
There was an error while loading. Please reload this page.