#!/usr/bin/env bash # # validate-paths.sh - Validates offline kit path structure # # Usage: ./validate-paths.sh [--combined] [kit_directory] # # Options: # --combined Expect combined runtime format (combined.runtime.ndjson) # kit_directory Path to kit directory (default: parent of this script) # # Exit codes: # 0 - All validations passed # 1 - Missing required files or directories # 2 - Invalid file format # 3 - Usage error set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" COMBINED_FORMAT=false KIT_DIR="" # Parse arguments while [[ $# -gt 0 ]]; do case "$1" in --combined) COMBINED_FORMAT=true shift ;; --help|-h) echo "Usage: $0 [--combined] [kit_directory]" echo "" echo "Validates offline kit path structure and file formats." echo "" echo "Options:" echo " --combined Expect combined runtime format" echo " kit_directory Path to kit directory (default: parent of this script)" exit 0 ;; -*) echo "Unknown option: $1" >&2 exit 3 ;; *) KIT_DIR="$1" shift ;; esac done # Default to parent directory if not specified if [[ -z "$KIT_DIR" ]]; then KIT_DIR="${SCRIPT_DIR}/.." fi # Resolve to absolute path KIT_DIR="$(cd "$KIT_DIR" && pwd)" echo "Validating kit at: $KIT_DIR" ERRORS=0 # Helper functions check_file() { local file="$1" local required="${2:-true}" local path="$KIT_DIR/$file" if [[ -f "$path" ]]; then echo " [OK] $file" return 0 elif [[ "$required" == "true" ]]; then echo " [MISSING] $file (required)" >&2 ERRORS=$((ERRORS + 1)) return 1 else echo " [SKIP] $file (optional)" return 0 fi } check_dir() { local dir="$1" local required="${2:-true}" local path="$KIT_DIR/$dir" if [[ -d "$path" ]]; then echo " [OK] $dir/" return 0 elif [[ "$required" == "true" ]]; then echo " [MISSING] $dir/ (required)" >&2 ERRORS=$((ERRORS + 1)) return 1 else echo " [SKIP] $dir/ (optional)" return 0 fi } validate_json() { local file="$1" local path="$KIT_DIR/$file" if [[ ! -f "$path" ]]; then return 0 # Skip if file doesn't exist (handled by check_file) fi if command -v python3 >/dev/null 2>&1; then if python3 -c "import json; json.load(open('$path'))" 2>/dev/null; then echo " [VALID JSON] $file" return 0 else echo " [INVALID JSON] $file" >&2 ERRORS=$((ERRORS + 1)) return 1 fi elif command -v jq >/dev/null 2>&1; then if jq empty "$path" 2>/dev/null; then echo " [VALID JSON] $file" return 0 else echo " [INVALID JSON] $file" >&2 ERRORS=$((ERRORS + 1)) return 1 fi else echo " [SKIP] $file (no JSON validator available)" return 0 fi } validate_ndjson() { local file="$1" local path="$KIT_DIR/$file" if [[ ! -f "$path" ]]; then return 0 # Skip if file doesn't exist fi if command -v python3 >/dev/null 2>&1; then local result result=$(python3 -c " import json, sys path = '$path' errors = 0 with open(path, 'r') as f: for i, line in enumerate(f, 1): line = line.strip() if not line: continue try: json.loads(line) except json.JSONDecodeError as e: print(f'Line {i}: {e}', file=sys.stderr) errors += 1 if errors >= 5: print('(truncated after 5 errors)', file=sys.stderr) break sys.exit(0 if errors == 0 else 1) " 2>&1) if [[ $? -eq 0 ]]; then echo " [VALID NDJSON] $file" return 0 else echo " [INVALID NDJSON] $file" >&2 echo "$result" >&2 ERRORS=$((ERRORS + 1)) return 1 fi else echo " [SKIP] $file (python3 required for NDJSON validation)" return 0 fi } # ============================================================================= # Directory Structure Validation # ============================================================================= echo "" echo "=== Checking directory structure ===" check_dir "schemas" check_dir "exports" check_dir "kit" # ============================================================================= # Core Files Validation # ============================================================================= echo "" echo "=== Checking core files ===" check_file "thresholds.yaml" check_file "thresholds.yaml.dsse" check_file "SHA256SUMS" # ============================================================================= # Schema Files Validation # ============================================================================= echo "" echo "=== Checking schema files ===" check_file "schemas/observer_event.schema.json" check_file "schemas/observer_event.schema.json.dsse" check_file "schemas/webhook_admission.schema.json" check_file "schemas/webhook_admission.schema.json.dsse" # ============================================================================= # Kit Files Validation # ============================================================================= echo "" echo "=== Checking kit files ===" check_file "kit/ed25519.pub" check_file "kit/verify.sh" check_file "kit/zastava-kit.tzst" false # Optional - may not be in source tree check_file "kit/zastava-kit.tzst.dsse" false # ============================================================================= # Export Files Validation # ============================================================================= echo "" echo "=== Checking export files ===" if [[ "$COMBINED_FORMAT" == "true" ]]; then # Combined format echo "(Combined format mode)" check_file "exports/combined.runtime.ndjson" check_file "exports/combined.runtime.ndjson.dsse" # Legacy files are optional in combined mode check_file "exports/observer_events.ndjson" false check_file "exports/webhook_admissions.ndjson" false else # Legacy format echo "(Legacy format mode)" check_file "exports/observer_events.ndjson" check_file "exports/observer_events.ndjson.dsse" check_file "exports/webhook_admissions.ndjson" check_file "exports/webhook_admissions.ndjson.dsse" # Combined is optional in legacy mode check_file "exports/combined.runtime.ndjson" false fi # ============================================================================= # JSON/NDJSON Format Validation # ============================================================================= echo "" echo "=== Validating file formats ===" validate_json "schemas/observer_event.schema.json" validate_json "schemas/webhook_admission.schema.json" if [[ "$COMBINED_FORMAT" == "true" ]] && [[ -f "$KIT_DIR/exports/combined.runtime.ndjson" ]]; then validate_ndjson "exports/combined.runtime.ndjson" else if [[ -f "$KIT_DIR/exports/observer_events.ndjson" ]]; then validate_ndjson "exports/observer_events.ndjson" fi if [[ -f "$KIT_DIR/exports/webhook_admissions.ndjson" ]]; then validate_ndjson "exports/webhook_admissions.ndjson" fi fi # ============================================================================= # Combined Format Structure Validation # ============================================================================= if [[ "$COMBINED_FORMAT" == "true" ]] && [[ -f "$KIT_DIR/exports/combined.runtime.ndjson" ]]; then echo "" echo "=== Validating combined format structure ===" if command -v python3 >/dev/null 2>&1; then python3 - "$KIT_DIR/exports/combined.runtime.ndjson" <<'PYTHON' import json import sys path = sys.argv[1] errors = [] has_header = False has_footer = False record_types = set() with open(path, 'r') as f: for i, line in enumerate(f, 1): line = line.strip() if not line: continue try: record = json.loads(line) rtype = record.get("type", "unknown") record_types.add(rtype) if rtype == "combined.header": if has_header: errors.append(f"Line {i}: duplicate header") has_header = True if i != 1: errors.append(f"Line {i}: header should be first record") elif rtype == "combined.footer": has_footer = True except json.JSONDecodeError as e: errors.append(f"Line {i}: {e}") if not has_header: errors.append("Missing combined.header record") if not has_footer: errors.append("Missing combined.footer record") if errors: for e in errors: print(f" [ERROR] {e}", file=sys.stderr) sys.exit(1) print(f" [OK] Header and footer present") print(f" [OK] Record types: {', '.join(sorted(record_types))}") PYTHON if [[ $? -ne 0 ]]; then ERRORS=$((ERRORS + 1)) fi fi fi # ============================================================================= # Summary # ============================================================================= echo "" echo "=== Validation Summary ===" if [[ $ERRORS -eq 0 ]]; then echo "All validations passed!" exit 0 else echo "$ERRORS validation error(s) found" >&2 exit 1 fi