diff --git a/pyxis/create_container_image.py b/pyxis/create_container_image.py index c990e49..05b9c32 100755 --- a/pyxis/create_container_image.py +++ b/pyxis/create_container_image.py @@ -140,21 +140,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 @@ -174,6 +168,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 @@ -250,19 +256,16 @@ 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 "digest" not in parsed_data: raise Exception("Digest was not found in the passed oras manifest json") if "name" not in parsed_data: raise Exception("Name was not found in the passed oras manifest json") - docker_image_digest = parsed_data["digest"] + + repositories = construct_repositories(args, parsed_data) + # digest isn't accepted in the parsed_data payload to pyxis del parsed_data["digest"] - image_name = parsed_data["name"] - image_registry = image_name.split("/")[0] - image_repo = image_name.split("/", 1)[1] # name isn't accepted in the parsed_data payload to pyxis del parsed_data["name"] @@ -278,15 +281,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"], @@ -296,24 +291,6 @@ def create_container_image(args, parsed_data: Dict[str, Any]): "uncompressed_top_layer_id": uncompressed_top_layer_id, } - container_image_payload["repositories"][0].update( - repository_digest_values(args, docker_image_digest) - ) - - # 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 @@ -331,25 +308,12 @@ 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") - - image_name = parsed_data["name"] - docker_image_digest = parsed_data["digest"] - patch_url = urljoin(args.pyxis_url, f"v1/images/id/{identifier}") - image["repositories"].append( - { - "published": True, - "registry": "registry.access.redhat.com", - "repository": proxymap(image_name), - "push_date": date_now, - "tags": pyxis_tags(args, date_now), - } - ) - image["repositories"][-1].update(repository_digest_values(args, docker_image_digest)) + payload = {"repositories": image["repositories"]} + payload["repositories"].extend(construct_repositories(args, parsed_data)) - rsp = pyxis.patch(patch_url, image).json() + rsp = pyxis.patch(patch_url, payload).json() # Make sure container metadata was successfully added to Pyxis if "_id" in rsp: @@ -358,6 +322,43 @@ def add_container_image_repository(args, parsed_data: Dict[str, Any], image: Dic 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] + + date_now = datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%f+00:00") + docker_image_digest = parsed_data["digest"] + + repos = [{ + "published": False, + "registry": image_registry, + "repository": image_repo, + "push_date": date_now, + "tags": pyxis_tags(args, date_now), + }] + + repos[0].update( + repository_digest_values(args, docker_image_digest) + ) + + # 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: + 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 """Main func""" @@ -369,11 +370,11 @@ def main(): # pragma: no cover parsed_data = prepare_parsed_data(args) # First check if it exists at all - 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"] - if image_already_exists(args, args.architecture_digest, repository=args.name): + # Then, check if it already references the given repository + if repo_in_image(args.name.split("/", 1)[-1], image): LOGGER.info( f"Image with given docker_image_digest already exists as {identifier} " f"and is associated with repository {args.name}. " diff --git a/pyxis/test_create_container_image.py b/pyxis/test_create_container_image.py index ed843d6..cc212bd 100644 --- a/pyxis/test_create_container_image.py +++ b/pyxis/test_create_container_image.py @@ -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, @@ -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 @@ -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 @@ -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 @@ -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 @@ -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