This is an automated email from the ASF dual-hosted git repository. arm pushed a commit to branch sbom_tests in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git
commit f4c170feb278c2b840a44768f1c674cc8a6ae004 Author: Alastair McFarlane <[email protected]> AuthorDate: Wed Dec 10 16:19:41 2025 +0000 Remove root dir check for sbom generation and add test case for generation function --- atr/tasks/sbom.py | 52 ++++++++++++++++++++++++++++++++++----------- tests/Dockerfile.e2e | 2 +- tests/e2e/sbom/__init__.py | 16 ++++++++++++++ tests/e2e/sbom/conftest.py | 47 ++++++++++++++++++++++++++++++++++++++++ tests/e2e/sbom/test_post.py | 40 ++++++++++++++++++++++++++++++++++ 5 files changed, 144 insertions(+), 13 deletions(-) diff --git a/atr/tasks/sbom.py b/atr/tasks/sbom.py index 6451f10..a32a9be 100644 --- a/atr/tasks/sbom.py +++ b/atr/tasks/sbom.py @@ -221,17 +221,17 @@ async def _generate_cyclonedx_core(artifact_path: str, output_path: str) -> dict async with util.async_temporary_directory(prefix="cyclonedx_sbom_") as temp_dir: log.info(f"Created temporary directory: {temp_dir}") - # Find and validate the root directory - try: - root_dir = await asyncio.to_thread(targz.root_directory, artifact_path) - except targz.RootDirectoryError as e: - raise SBOMGenerationError(f"Archive root directory issue: {e}", {"artifact_path": artifact_path}) from e - except Exception as e: - raise SBOMGenerationError( - f"Failed to determine archive root directory: {e}", {"artifact_path": artifact_path} - ) from e - - extract_dir = os.path.join(temp_dir, root_dir) + # # Find and validate the root directory + # try: + # root_dir = await asyncio.to_thread(targz.root_directory, artifact_path) + # except targz.RootDirectoryError as e: + # raise SBOMGenerationError(f"Archive root directory issue: {e}", {"artifact_path": artifact_path}) from e + # except Exception as e: + # raise SBOMGenerationError( + # f"Failed to determine archive root directory: {e}", {"artifact_path": artifact_path} + # ) from e + # + # extract_dir = os.path.join(temp_dir, root_dir) # Extract the archive to the temporary directory # TODO: Ideally we'd have task dependencies or archive caching @@ -243,7 +243,18 @@ async def _generate_cyclonedx_core(artifact_path: str, output_path: str) -> dict max_size=_CONFIG.MAX_EXTRACT_SIZE, chunk_size=_CONFIG.EXTRACT_CHUNK_SIZE, ) - log.info(f"Extracted {extracted_size} bytes into {extract_dir}") + log.info(f"Extracted {extracted_size} bytes") + + # Find the root directory + if (extract_dir := _extracted_dir(str(temp_dir))) is None: + log.error("No root directory found in archive") + return { + "valid": False, + "message": "No root directory found in archive", + "errors": [], + } + + log.info(f"Using root directory: {extract_dir}") # Run syft to generate the CycloneDX SBOM syft_command = ["syft", extract_dir, "-o", "cyclonedx-json"] @@ -302,3 +313,20 @@ async def _generate_cyclonedx_core(artifact_path: str, output_path: str) -> dict except FileNotFoundError: log.error("syft command not found. Is it installed and in PATH?") raise SBOMGenerationError("syft command not found") + +def _extracted_dir(temp_dir: str) -> str | None: + # Loop through all the dirs in temp_dir + extract_dir = None + log.info(f"Checking directories in {temp_dir}: {os.listdir(temp_dir)}") + for dir_name in os.listdir(temp_dir): + if dir_name.startswith("."): + continue + dir_path = os.path.join(temp_dir, dir_name) + if os.path.isdir(dir_path): + if extract_dir is None: + extract_dir = dir_path + else: + raise ValueError(f"Multiple root directories found: {extract_dir}, {dir_path}") + if extract_dir is None: + extract_dir = temp_dir + return extract_dir diff --git a/tests/Dockerfile.e2e b/tests/Dockerfile.e2e index a0c0ded..a66133b 100644 --- a/tests/Dockerfile.e2e +++ b/tests/Dockerfile.e2e @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/playwright/python:v1.56.0-noble +FROM mcr.microsoft.com/playwright/python:v1.57.0-noble RUN pip3 install --no-cache-dir --break-system-packages pytest pytest-playwright diff --git a/tests/e2e/sbom/__init__.py b/tests/e2e/sbom/__init__.py new file mode 100644 index 0000000..13a8339 --- /dev/null +++ b/tests/e2e/sbom/__init__.py @@ -0,0 +1,16 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. diff --git a/tests/e2e/sbom/conftest.py b/tests/e2e/sbom/conftest.py new file mode 100644 index 0000000..649b926 --- /dev/null +++ b/tests/e2e/sbom/conftest.py @@ -0,0 +1,47 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +from __future__ import annotations + +from typing import TYPE_CHECKING + +import e2e.helpers as helpers # type: ignore[reportMissingImports] +import pytest +import pathlib + +if TYPE_CHECKING: + from collections.abc import Generator + + from playwright.sync_api import Page + +PROJECT_NAME='test' +VERSION_NAME='0.1' +FILE_NAME='pyproject.tar.gz' +CURRENT_DIR=pathlib.Path(__file__).parent.resolve() + [email protected] +def page_release_with_file(page: Page) -> Generator[Page]: + helpers.log_in(page) + helpers.visit(page, f"/start/{PROJECT_NAME}") + page.get_by_role('textbox').type(VERSION_NAME) + page.get_by_role('button', name='Start new release').click() + helpers.visit(page, f"/upload/{PROJECT_NAME}/{VERSION_NAME}") + page.locator('input[name="file_data"]').set_input_files(f"{CURRENT_DIR}/../test_files/{FILE_NAME}") + page.get_by_role("button", name="Add files").click() + helpers.visit(page, f"/compose/{PROJECT_NAME}/{VERSION_NAME}") + page.wait_for_selector('#ongoing-tasks-banner',state='hidden') + yield page diff --git a/tests/e2e/sbom/test_post.py b/tests/e2e/sbom/test_post.py new file mode 100644 index 0000000..4c6165b --- /dev/null +++ b/tests/e2e/sbom/test_post.py @@ -0,0 +1,40 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +from playwright.sync_api import Page, expect + +import e2e.helpers as helpers # type: ignore[reportMissingImports] +from e2e.sbom.conftest import PROJECT_NAME,VERSION_NAME,FILE_NAME # type: ignore[reportMissingImports] + +def test_sbom_generate(page_release_with_file: Page) -> None: + # Make sure test file exists + file_cell = page_release_with_file.get_by_role('cell', name=FILE_NAME) + expect(file_cell).to_be_visible() + + # Generate an SBOM for the file + helpers.visit(page_release_with_file, f"/draft/tools/{PROJECT_NAME}/{VERSION_NAME}/{FILE_NAME}") + generate_button = page_release_with_file.get_by_role('button', name='SBOM') + generate_button.click() + + # Check the generated SBOM exists now + helpers.visit(page_release_with_file, f"/compose/{PROJECT_NAME}/{VERSION_NAME}") + page_release_with_file.wait_for_selector('#ongoing-tasks-banner',state='hidden') + page_release_with_file.reload() + + sbom_cell = page_release_with_file.get_by_role('cell', name=f"{FILE_NAME}.cdx.json") + expect(sbom_cell).to_be_visible() + --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
