You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tooling.apache.org by ar...@apache.org on 2025/12/10 16:39:36 UTC
(tooling-trusted-releases) 01/01: Remove root dir check for sbom generation and add test case for generation function
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 <ar...@apache.org>
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()
+
+@pytest.fixture
+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: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org