Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/Docusaurus/docs/modules/engine_core.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ Configuration of the driven adapters in the main layer and management of on/off
"LIFECYCLE_PIPELINES": {
"pipeline_name1": "pre-build"
},
"BREAK_ON_BUILD_FAILURE": true,
"BUILD_FAILURE_PATTERNS": ["BUILD FAILED", "BUILD FAILURE", "npm ERR!"],
"OVERRIDE_REGISTRIES": false,
"REGISTRIES": {
"MAVEN_CENTRAL_URL": "",
Expand Down Expand Up @@ -368,6 +370,8 @@ Configuration of the driven adapters in the main layer and management of on/off
- **CDXGEN**
- **FETCH_LICENSE**: `true` or `false`. When enabled, cdxgen fetches license information for each component from public registries and includes it in the generated SBOM. Recommended when `--use_license_analyzer true` is used.
- **INSTALL_DEPENDENCIES**: `true` or `false`. When enabled, cdxgen installs project dependencies before generating the SBOM, improving component coverage.
- **BREAK_ON_BUILD_FAILURE**: `true` or `false` (default `true`). cdxgen can exit with return code `0` even when the underlying build tool actually failed, silently producing an incomplete/empty SBOM. When enabled, the stdout/stderr of the cdxgen process is checked against `BUILD_FAILURE_PATTERNS` and, if any pattern matches, the SBOM generation is aborted and the error is logged so the affected pipeline is easy to spot.
- **BUILD_FAILURE_PATTERNS**: Array of regular expressions (case-insensitive) used to detect build failures in the cdxgen output. There are no built-in defaults; patterns must be defined entirely via remote config for the languages/build tools relevant to your pipelines. Example: `["BUILD FAILED", "BUILD FAILURE", "npm ERR!"]`.
- **OVERRIDE_REGISTRIES**: `true` or `false`. When enabled, the registry URLs defined in `REGISTRIES` are set as environment variables before cdxgen runs, redirecting dependency resolution to internal or private registries.
- **REGISTRIES**: Map of environment variable names to registry URLs used when `OVERRIDE_REGISTRIES` is `true`.
- **MAVEN_CENTRAL_URL**: Maven registry URL.
Expand Down
2 changes: 2 additions & 0 deletions example_remote_config_local/engine_core/ConfigTool.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@
"LIFECYCLE_PIPELINES": {
"pipeline_name1": "pre-build"
},
"BREAK_ON_BUILD_FAILURE": true,
"BUILD_FAILURE_PATTERNS": ["BUILD FAILED", "BUILD FAILURE", "npm ERR!"],
"OVERRIDE_REGISTRIES": false,
"REGISTRIES": {
"MAVEN_CENTRAL_URL": "https://repo1.maven.org/maven2/",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import subprocess
import platform
import os
import re

from devsecops_engine_tools.engine_core.src.domain.model.gateway.sbom_manager import (
SbomManagerGateway,
Expand Down Expand Up @@ -35,6 +36,9 @@
debug_pipelines = config["CDXGEN"].get("DEBUG_PIPELINES", [])
lifecycle_pipelines = config["CDXGEN"].get("LIFECYCLE_PIPELINES", {})
spec_version = config["CDXGEN"].get("SPEC_VERSION", "1.6")
break_on_build_failure = config["CDXGEN"].get("BREAK_ON_BUILD_FAILURE", True)
build_failure_patterns = config["CDXGEN"].get("BUILD_FAILURE_PATTERNS", [])
failure_patterns = build_failure_patterns if break_on_build_failure else []

if config["CDXGEN"].get("OVERRIDE_REGISTRIES", False):
registries = config["CDXGEN"].get("REGISTRIES", {})
Expand Down Expand Up @@ -86,13 +90,14 @@
logger.warning(f"{os_platform} is not supported.")
return None

result_sbom = self._run_cdxgen(command_prefix, artifact, service_name, exclude_types, exclude_paths, recurse, install_deps, lifecycle_pipelines, enable_debug, spec_version)
result_sbom = self._run_cdxgen(command_prefix, artifact, service_name, exclude_types, exclude_paths, recurse, install_deps, lifecycle_pipelines, enable_debug, spec_version, failure_patterns)
return get_list_component(result_sbom, config["CDXGEN"]["OUTPUT_FORMAT"])
except Exception as e:
logger.error(f"Error generating SBOM: {e}")
return None

def _run_cdxgen(self, command_prefix, artifact, service_name, exclude_types, exclude_paths, recurse, install_deps, lifecycle_pipelines, enable_debug=False, spec_version="1.6"):
def _run_cdxgen(self, command_prefix, artifact, service_name, exclude_types, exclude_paths, recurse, install_deps, lifecycle_pipelines, enable_debug=False, spec_version="1.6", failure_patterns=None):

Check failure on line 99 in tools/devsecops_engine_tools/engine_core/src/infrastructure/driven_adapters/cdxgen/cdxgen.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 21 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=bancolombia_devsecops-engine-tools&issues=AZ8jW4BtQux0nilqj1rg&open=AZ8jW4BtQux0nilqj1rg&pullRequest=672
failure_patterns = failure_patterns or []
result_file = f"{service_name}_SBOM.json"
command = [
command_prefix,
Expand Down Expand Up @@ -143,7 +148,18 @@
logger.info(f"CDXGEN stdout: {result.stdout}")
if result.stderr:
logger.info(f"CDXGEN stderr: {result.stderr}")


matched_pattern = self._detect_build_failure(
f"{result.stdout or ''}\n{result.stderr or ''}", failure_patterns
)
if matched_pattern:
self._remove_incomplete_sbom(result_file)
raise Exception(
f"Detected build failure pattern '{matched_pattern}' in cdxgen output for "
f"'{service_name}'. The underlying build likely failed; aborting to avoid "
"generating an incomplete or empty SBOM."
)

Check warning on line 161 in tools/devsecops_engine_tools/engine_core/src/infrastructure/driven_adapters/cdxgen/cdxgen.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this generic exception class with a more specific one.

See more on https://sonarcloud.io/project/issues?id=bancolombia_devsecops-engine-tools&issues=AZ8jW4BtQux0nilqj1rh&open=AZ8jW4BtQux0nilqj1rh&pullRequest=672

if result.returncode == 0:
print(f"SBOM generated and saved to: {result_file}")
return result_file
Expand All @@ -153,6 +169,27 @@
except Exception as e:
logger.error(f"Error running cdxgen: {e}")

def _detect_build_failure(self, output, patterns):
"""Return the first configured regex pattern that matches the cdxgen output, if any."""
if not output or not patterns:
return None
for pattern in patterns:
try:
if re.search(pattern, output, re.IGNORECASE):
return pattern
except re.error as e:
logger.debug(f"Invalid build failure regex pattern '{pattern}': {e}")
return None

def _remove_incomplete_sbom(self, result_file):
"""Best-effort removal of a possibly incomplete/empty SBOM file left by a failed build."""
try:
if result_file and os.path.exists(result_file):
os.remove(result_file)
logger.info(f"Removed incomplete SBOM file: {result_file}")
except OSError as e:
logger.debug(f"Could not remove incomplete SBOM file '{result_file}': {e}")

def _check_cdxgen_in_path(self):
"""Check if cdxgen is available in PATH and return its path if found."""
try:
Expand Down
Loading
Loading