Skip to content

Commit

Permalink
refactor: patching ContainerImage to add repositories
Browse files Browse the repository at this point in the history
This is a followup of what was done in konflux-ci#303

Most notably:

- rename the image_already_exists to find_image
  to reflect what it does - it used to return bool, now it returns
  the image if found.
- only include the repositories field in the patch payload
- for consistency, create the same two repo entries when adding
  a new repository like we do when creating a brand new image

Signed-off-by: Martin Malina <[email protected]>
  • Loading branch information
mmalina committed Nov 22, 2024
1 parent 5e9ee4c commit be2099f
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 65 deletions.
119 changes: 61 additions & 58 deletions pyxis/create_container_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,21 +149,15 @@ def proxymap(repository: str) -> str:
return repository.split("/")[-1].replace("----", "/")


def image_already_exists(args, digest: str, repository: str) -> Any:
"""Function to check if a containerImage with the given digest and repository
already exists in the pyxis instance
def find_image(args, digest: str) -> Any:
"""Function to find a containerImage with the given digest.
If `repository` is None, then the return True if the image exists at all.
:return: the image id, if one exists, else None if not found
:return: the image, if one exists, else None if none found
"""

# quote is needed to urlparse the quotation marks
raw_filter = f'repositories.manifest_schema2_digest=="{digest}";not(deleted==true)'
if repository:
raw_filter += f';repositories.repository=="{proxymap(repository)}"'
# quote is needed to urlparse the quotation marks
filter_str = quote(raw_filter)

check_url = urljoin(args.pyxis_url, f"v1/images?page_size=1&filter={filter_str}")

# Get the list of the ContainerImages with given parameters
Expand All @@ -183,6 +177,18 @@ def image_already_exists(args, digest: str, repository: str) -> Any:
return query_results[0]


def repo_in_image(repository_str: str, image: Dict[str, Any]) -> bool:
"""Check if a repository already exists in the ContainerImage
:return: True if repository_str string is found in the ContainerImage repositories,
False otherwise
"""
for repository in image["repositories"]:
if repository["repository"] == repository_str:
return True
return False


def prepare_parsed_data(args) -> Dict[str, Any]:
"""Function to extract the data this script needs from provided oras manifest fetch output
Expand Down Expand Up @@ -259,17 +265,14 @@ def create_container_image(args, parsed_data: Dict[str, Any]):

LOGGER.info("Creating new container image")

date_now = datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%f+00:00")

if "name" not in parsed_data:
raise Exception("Name was not found in the passed oras manifest json")

# digest isn't accepted in the parsed_data payload to pyxis; we ignore it
del parsed_data["digest"]

image_name = parsed_data["name"]
image_registry = image_name.split("/")[0]
image_repo = image_name.split("/", 1)[1]
repositories = construct_repositories(args, parsed_data)

# name isn't accepted in the parsed_data payload to pyxis
del parsed_data["name"]

Expand All @@ -285,15 +288,7 @@ def create_container_image(args, parsed_data: Dict[str, Any]):
upload_url = urljoin(args.pyxis_url, "v1/images")

container_image_payload = {
"repositories": [
{
"published": False,
"registry": image_registry,
"repository": image_repo,
"push_date": date_now,
"tags": pyxis_tags(args, date_now),
}
],
"repositories": repositories,
"certified": json.loads(args.certified.lower()),
"image_id": args.architecture_digest,
"architecture": parsed_data["architecture"],
Expand All @@ -307,22 +302,6 @@ def create_container_image(args, parsed_data: Dict[str, Any]):
if uncompressed_top_layer_id:
container_image_payload["uncompressed_top_layer_id"] = uncompressed_top_layer_id

container_image_payload["repositories"][0].update(repository_digest_values(args))

# For images released to registry.redhat.io we need a second repository item
# with published=true and registry and repository converted.
# E.g. if the name in the oras manifest result is
# "quay.io/redhat-prod/rhtas-tech-preview----cosign-rhel9",
# repository will be "rhtas-tech-preview/cosign-rhel9"
if not args.rh_push == "true":
LOGGER.info("--rh-push is not set. Skipping public registry association.")
else:
repo = container_image_payload["repositories"][0].copy()
repo["published"] = True
repo["registry"] = "registry.access.redhat.com"
repo["repository"] = proxymap(image_name)
container_image_payload["repositories"].append(repo)

rsp = pyxis.post(upload_url, container_image_payload).json()

# Make sure container metadata was successfully added to Pyxis
Expand All @@ -340,30 +319,55 @@ def add_container_image_repository(args, parsed_data: Dict[str, Any], image: Dic
identifier = image["_id"]
LOGGER.info(f"Adding repository to container image {identifier}")

date_now = datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%f+00:00")
patch_url = urljoin(args.pyxis_url, f"v1/images/id/{identifier}")

payload = {"repositories": image["repositories"]}
payload["repositories"].extend(construct_repositories(args, parsed_data))

rsp = pyxis.patch(patch_url, payload).json()

# Make sure container metadata was successfully added to Pyxis
if "_id" in rsp:
emit_id(rsp["_id"])
else:
raise Exception("Image metadata was not successfully added to Pyxis.")


def construct_repositories(args, parsed_data):
image_name = parsed_data["name"]
image_registry = image_name.split("/")[0]
image_repo = image_name.split("/", 1)[1]

patch_url = urljoin(args.pyxis_url, f"v1/images/id/{identifier}")
date_now = datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%f+00:00")
docker_image_digest = parsed_data["digest"]

image["repositories"].append(
repos = [
{
"published": True,
"registry": "registry.access.redhat.com",
"repository": proxymap(image_name),
"published": False,
"registry": image_registry,
"repository": image_repo,
"push_date": date_now,
"tags": pyxis_tags(args, date_now),
}
)
image["repositories"][-1].update(repository_digest_values(args))
]

rsp = pyxis.patch(patch_url, image).json()
repos[0].update(repository_digest_values(args, docker_image_digest))

# Make sure container metadata was successfully added to Pyxis
if "_id" in rsp:
emit_id(rsp["_id"])
# For images released to registry.redhat.io we need a second repository item
# with published=true and registry and repository converted.
# E.g. if the name in the oras manifest result is
# "quay.io/redhat-prod/rhtas-tech-preview----cosign-rhel9",
# repository will be "rhtas-tech-preview/cosign-rhel9"
if not args.rh_push == "true":
LOGGER.info("--rh-push is not set. Skipping public registry association.")
else:
raise Exception("Image metadata was not successfully added to Pyxis.")
rh_repo = repos[0].copy()
rh_repo["published"] = True
rh_repo["registry"] = "registry.access.redhat.com"
rh_repo["repository"] = proxymap(image_name)
repos.append(rh_repo)

return repos


def main(): # pragma: no cover
Expand All @@ -378,12 +382,11 @@ def main(): # pragma: no cover

# First check if it exists at all
LOGGER.info(f"Checking to see if digest {args.architecture_digest} exists in pyxis")
image = image_already_exists(args, args.architecture_digest, repository=None)
if image:
# Then, check if it exists in association with the given repository
image = find_image(args, args.architecture_digest)
if image is not None:
identifier = image["_id"]
LOGGER.info(f"It does! Checking to see if it's associated with {args.name}")
if image_already_exists(args, args.architecture_digest, repository=args.name):
# Then, check if it already references the given repository
if repo_in_image(proxymap(args.name), image):
LOGGER.info(
f"Image with given docker_image_digest already exists as {identifier} "
f"and is associated with repository {args.name}. "
Expand Down
14 changes: 7 additions & 7 deletions pyxis/test_create_container_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from unittest.mock import patch, MagicMock

from create_container_image import (
image_already_exists,
find_image,
create_container_image,
add_container_image_repository,
prepare_parsed_data,
Expand All @@ -15,7 +15,7 @@


@patch("create_container_image.pyxis.get")
def test_image_already_exists__image_does_exist(mock_get):
def test_find_image__image_does_exist(mock_get):
# Arrange
mock_rsp = MagicMock()
mock_get.return_value = mock_rsp
Expand All @@ -28,7 +28,7 @@ def test_image_already_exists__image_does_exist(mock_get):
mock_rsp.json.return_value = {"data": [{"_id": 0}]}

# Act
exists = image_already_exists(args, args.architecture_digest, args.name)
exists = find_image(args, args.architecture_digest, args.name)

# Assert
assert exists
Expand All @@ -42,7 +42,7 @@ def test_image_already_exists__image_does_exist(mock_get):


@patch("create_container_image.pyxis.get")
def test_image_already_exists__image_does_not_exist(mock_get):
def test_find_image__image_does_not_exist(mock_get):
# Arrange
mock_rsp = MagicMock()
mock_get.return_value = mock_rsp
Expand All @@ -55,14 +55,14 @@ def test_image_already_exists__image_does_not_exist(mock_get):
mock_rsp.json.return_value = {"data": []}

# Act
exists = image_already_exists(args, digest, name)
exists = find_image(args, digest, name)

# Assert
assert not exists


@patch("create_container_image.pyxis.get")
def test_image_already_exists__image_does_exist_but_no_repo(mock_get):
def test_find_image__image_does_exist_but_no_repo(mock_get):
# Arrange
mock_rsp = MagicMock()
mock_get.return_value = mock_rsp
Expand All @@ -75,7 +75,7 @@ def test_image_already_exists__image_does_exist_but_no_repo(mock_get):
mock_rsp.json.return_value = {"data": [{"_id": 0}]}

# Act
exists = image_already_exists(args, args.architecture_digest, None)
exists = find_image(args, args.architecture_digest, None)

# Assert
assert exists
Expand Down

0 comments on commit be2099f

Please sign in to comment.