Skip to content
Open
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
18 changes: 12 additions & 6 deletions src/cli.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import argparse
import logging
import sys
from .controllers.cli_controller import CLIController

logger = logging.getLogger(__name__)

def main():
parser = argparse.ArgumentParser(description="OWASP AIBOM Generator CLI")
parser.add_argument("model_id", nargs="?", help="Hugging Face Model ID (e.g. 'owner/model')")
Expand All @@ -13,9 +16,12 @@ def main():
parser.add_argument("--name", "-n", help="Component name in metadata")
parser.add_argument("--version", "-v", help="Component version in metadata")
parser.add_argument("--manufacturer", "-m", help="Component manufacturer/supplier in metadata")

args = parser.parse_args()


log_level = logging.DEBUG if args.verbose else logging.INFO
logging.basicConfig(level=log_level, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s")

controller = CLIController()

if args.test:
Expand All @@ -36,22 +42,22 @@ def main():
"CIRCL/vulnerability-severity-classification-roberta-base"
]

print(f"Running test mode against {len(test_models)} models...")
logger.info("Running test mode against %d models...", len(test_models))
for model in test_models:
print(f"\n{'='*50}\nTesting model: {model}\n{'='*50}")
logger.info("Testing model: %s", model)
try:
controller.generate(
model_id=model,
output_file=args.output,
include_inference=args.inference,
enable_summarization=True, # Ensure summarization is on for testing description
enable_summarization=True, # Ensure summarization is on for testing description
verbose=args.verbose,
name=args.name,
version=args.version,
manufacturer=args.manufacturer
)
except Exception as e:
print(f"Error testing {model}: {e}")
logger.error("Error testing %s: %s", model, e)
sys.exit(0)

if not args.model_id:
Expand Down
61 changes: 28 additions & 33 deletions src/controllers/cli_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from typing import Optional
from ..models.service import AIBOMService
from ..models.scoring import calculate_completeness_score
from ..models.scoring import calculate_completeness_score
from ..config import OUTPUT_DIR, TEMPLATES_DIR
from ..utils.formatter import export_aibom
import os
Expand All @@ -25,15 +24,15 @@ def generate(self, model_id: str, output_file: Optional[str] = None, include_inf
enable_summarization: bool = False, verbose: bool = False,
name: Optional[str] = None, version: Optional[str] = None, manufacturer: Optional[str] = None):
if verbose:
logging.getLogger().setLevel(logging.INFO)
print(f"Generating AIBOM for {model_id}...")
logging.getLogger().setLevel(logging.DEBUG)

logger.info("Generating AIBOM for %s...", model_id)

versions_to_generate = ["1.6", "1.7"]
reports = []
generated_aiboms = {}
print(f" - Generating AIBOM model data...")
generated_aiboms = {}

logger.info("Generating AIBOM model data...")
try:
primary_aibom = self.service.generate_aibom(
model_id,
Expand Down Expand Up @@ -85,8 +84,7 @@ def generate(self, model_id: str, output_file: Optional[str] = None, include_inf
output_file_primary = output_file_1_6

except Exception as e:
logger.error(f"Failed to generate SBOM: {e}", exc_info=True)
print(f" ❌ Failed to generate SBOM: {e}")
logger.error("Failed to generate SBOM: %s", e, exc_info=True)
reports = []

if reports:
Expand Down Expand Up @@ -130,7 +128,7 @@ def generate(self, model_id: str, output_file: Optional[str] = None, include_inf
with open(html_output_file, "w") as f:
f.write(html_content)

print(f"\n📄 HTML Report:\n {html_output_file}")
logger.info("HTML Report: %s", html_output_file)

# Copy static assets
try:
Expand All @@ -148,19 +146,19 @@ def generate(self, model_id: str, output_file: Optional[str] = None, include_inf
if os.path.exists(static_dst):
shutil.rmtree(static_dst)
shutil.copytree(static_src, static_dst)
# print(f" - Static assets copied to: {static_dst}")
logger.debug("Static assets copied to: %s", static_dst)
else:
logger.warning(f"Static source directory not found: {static_src}")
logger.warning("Static source directory not found: %s", static_src)

except Exception as e:
logger.warning(f"Failed to copy static assets: {e}")
logger.warning("Failed to copy static assets: %s", e)

# Model Description
if "components" in primary_aibom and primary_aibom["components"]:
description = primary_aibom["components"][0].get("description", "No description available")
if len(description) > 256:
description = description[:253] + "..."
print(f"\n📝 Model Description:\n {description}")
logger.info("Model Description: %s", description)

# License
if "components" in primary_aibom and primary_aibom["components"]:
Expand All @@ -173,42 +171,39 @@ def generate(self, model_id: str, output_file: Optional[str] = None, include_inf
if val:
license_list.append(val)
if license_list:
print(f"\n⚖️ License:\n {', '.join(license_list)}")
logger.info("License: %s", ", ".join(license_list))

except Exception as e:
logger.warning(f"Failed to generate HTML report: {e}")
logger.warning("Failed to generate HTML report: %s", e)

# Print Summary for ALL versions
for r in reports:
spec = r.get("spec_version", "1.6")
print(f"\n✅ Successfully generated CycloneDX {spec} SBOM:")
print(f" {r.get('output_file')}")

logger.info("Successfully generated CycloneDX %s SBOM: %s", spec, r.get("output_file"))

if not r["schema_validation"]["valid"]:
print(f"⚠️ Schema Validation Errors ({spec}):")
logger.warning("Schema Validation Errors (%s):", spec)
for err in r["schema_validation"]["errors"]:
print(f" - {err}")
logger.warning(" - %s", err)
else:
print(f" - Schema Validation ({spec}): Valid")
logger.info("Schema Validation (%s): Valid", spec)

# Display Detailed Score Summary (from primary)
if primary_report and "final_score" in primary_report:
score = primary_report["final_score"]
t_score = score.get('total_score', 0)
formatted_t_score = int(t_score) if isinstance(t_score, (int, float)) and t_score == int(t_score) else t_score
print(f"\n📊 Completeness Score: {formatted_t_score}/100")
logger.info("Completeness Score: %s/100", formatted_t_score)

if "completeness_profile" in score:
profile = score["completeness_profile"]
print(f" Profile: {profile.get('name')} - {profile.get('description')}")
logger.info("Profile: %s - %s", profile.get("name"), profile.get("description"))

if "section_scores" in score:
print("\n📋 Section Breakdown:")

logger.info("Section Breakdown:")
for section, s_score in score["section_scores"].items():
max_s = score.get("max_scores", {}).get(section, "?")
formatted_s_score = int(s_score) if isinstance(s_score, (int, float)) and s_score == int(s_score) else s_score
print(f" - {section.replace('_', ' ').title()}: {formatted_s_score}/{max_s}")
logger.info(" %s: %s/%s", section.replace("_", " ").title(), formatted_s_score, max_s)

else:
print("\n❌ Failed to generate any SBOMs.")
logger.error("Failed to generate any SBOMs.")
3 changes: 1 addition & 2 deletions src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,5 @@ async def cleanup_middleware(request: Request, call_next):

if __name__ == "__main__":
import uvicorn
# Print clear access URL to avoid 0.0.0.0 confusion
print("🚀 Application ready! Access it at: http://localhost:8000")
logger.info("Application ready! Access it at: http://localhost:8000")
uvicorn.run("src.main:app", host="0.0.0.0", port=8000, reload=True)
Loading