278 lines
7.2 KiB
Bash
278 lines
7.2 KiB
Bash
#!/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 <spdx-file>
|
|
# ./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] <spdx-file>
|
|
|
|
Validates SPDX files against SPDX 3.0.1 JSON schema.
|
|
|
|
Options:
|
|
--all Validate all SPDX fixtures in bench/golden-corpus/
|
|
--schema <path> 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 "$@"
|