#!/bin/bash # scripts/validate-spdx.sh # Sprint: SPRINT_8200_0001_0003 - SBOM Schema Validation in CI # Task: SCHEMA-8200-005 - Create validate-spdx.sh wrapper for SPDX validation # # Validates SPDX files against SPDX 3.0.1 JSON schema. # Uses pyspdxtools (spdx-tools) for SPDX validation. # # Usage: # ./scripts/validate-spdx.sh # ./scripts/validate-spdx.sh bench/golden-corpus/sample.spdx.json # ./scripts/validate-spdx.sh --all # Validate all SPDX fixtures # # Exit codes: # 0 - All validations passed # 1 - Validation failed or error set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" SCHEMA_DIR="${REPO_ROOT}/docs/schemas" DEFAULT_SCHEMA="${SCHEMA_DIR}/spdx-jsonld-3.0.1.schema.json" # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color log_info() { echo -e "${GREEN}[INFO]${NC} $*" } log_warn() { echo -e "${YELLOW}[WARN]${NC} $*" } log_error() { echo -e "${RED}[ERROR]${NC} $*" } check_spdx_tools() { if ! command -v pyspdxtools &> /dev/null; then log_warn "pyspdxtools not found in PATH" log_info "Installing spdx-tools via pip..." if command -v pip3 &> /dev/null; then pip3 install --user spdx-tools elif command -v pip &> /dev/null; then pip install --user spdx-tools else log_error "pip not found. Please install Python and pip first." exit 1 fi log_info "spdx-tools installed successfully" # Refresh PATH for newly installed tools if [[ -d "${HOME}/.local/bin" ]]; then export PATH="${HOME}/.local/bin:${PATH}" fi fi } check_ajv() { if ! command -v ajv &> /dev/null; then log_warn "ajv-cli not found in PATH" log_info "Installing ajv-cli via npm..." if command -v npm &> /dev/null; then npm install -g ajv-cli ajv-formats else log_warn "npm not found. JSON schema validation will be skipped." return 1 fi log_info "ajv-cli installed successfully" fi return 0 } validate_spdx_schema() { local spdx_file="$1" local schema="$2" if check_ajv; then log_info "Validating against JSON schema: $schema" if ajv validate -s "$schema" -d "$spdx_file" --spec=draft2020 2>&1; then return 0 else return 1 fi else log_warn "Skipping JSON schema validation (ajv not available)" return 0 fi } validate_spdx() { local spdx_file="$1" local schema="${2:-$DEFAULT_SCHEMA}" if [[ ! -f "$spdx_file" ]]; then log_error "File not found: $spdx_file" return 1 fi # Detect if it's an SPDX file (JSON-LD format) if ! grep -qE '"@context"|"spdxId"|"spdxVersion"' "$spdx_file" 2>/dev/null; then log_warn "File does not appear to be SPDX: $spdx_file" log_info "Skipping (use validate-sbom.sh for CycloneDX files)" return 0 fi log_info "Validating: $spdx_file" local validation_passed=true # Try pyspdxtools validation first (semantic validation) if command -v pyspdxtools &> /dev/null; then log_info "Running SPDX semantic validation..." if pyspdxtools validate "$spdx_file" 2>&1; then log_info "✓ SPDX semantic validation passed" else # pyspdxtools may not support SPDX 3.0 yet log_warn "pyspdxtools validation failed or not supported for this format" log_info "Falling back to JSON schema validation only" fi fi # JSON schema validation (syntax validation) if [[ -f "$schema" ]]; then if validate_spdx_schema "$spdx_file" "$schema"; then log_info "✓ JSON schema validation passed" else log_error "✗ JSON schema validation failed" validation_passed=false fi else log_warn "Schema file not found: $schema" log_info "Skipping schema validation" fi if [[ "$validation_passed" == "true" ]]; then log_info "✓ Validation passed: $spdx_file" return 0 else log_error "✗ Validation failed: $spdx_file" return 1 fi } validate_all() { local fixture_dir="${REPO_ROOT}/bench/golden-corpus" local failed=0 local passed=0 local skipped=0 log_info "Validating all SPDX fixtures in ${fixture_dir}..." if [[ ! -d "$fixture_dir" ]]; then log_error "Fixture directory not found: $fixture_dir" return 1 fi while IFS= read -r -d '' file; do # Check if it's an SPDX file if grep -qE '"@context"|"spdxVersion"' "$file" 2>/dev/null; then if validate_spdx "$file"; then ((passed++)) else ((failed++)) fi else log_info "Skipping non-SPDX file: $file" ((skipped++)) fi done < <(find "$fixture_dir" -type f \( -name '*spdx*.json' -o -name '*.spdx.json' \) -print0) echo "" log_info "Validation Summary:" log_info " Passed: ${passed}" log_info " Failed: ${failed}" log_info " Skipped: ${skipped}" if [[ $failed -gt 0 ]]; then log_error "Some validations failed!" return 1 fi log_info "All SPDX validations passed!" return 0 } usage() { cat << EOF Usage: $(basename "$0") [OPTIONS] Validates SPDX files against SPDX 3.0.1 JSON schema. Options: --all Validate all SPDX fixtures in bench/golden-corpus/ --schema Use custom schema file (default: docs/schemas/spdx-jsonld-3.0.1.schema.json) --help, -h Show this help message Examples: $(basename "$0") sample.spdx.json $(basename "$0") --schema custom-schema.json sample.json $(basename "$0") --all Exit codes: 0 All validations passed 1 Validation failed or error EOF } main() { local schema="$DEFAULT_SCHEMA" local validate_all_flag=false local files=() while [[ $# -gt 0 ]]; do case "$1" in --all) validate_all_flag=true shift ;; --schema) schema="$2" shift 2 ;; --help|-h) usage exit 0 ;; -*) log_error "Unknown option: $1" usage exit 1 ;; *) files+=("$1") shift ;; esac done # Ensure tools are available check_spdx_tools || true # Continue even if pyspdxtools install fails if [[ "$validate_all_flag" == "true" ]]; then validate_all exit $? fi if [[ ${#files[@]} -eq 0 ]]; then log_error "No SPDX file specified" usage exit 1 fi local failed=0 for file in "${files[@]}"; do if ! validate_spdx "$file" "$schema"; then ((failed++)) fi done if [[ $failed -gt 0 ]]; then exit 1 fi exit 0 } main "$@"