Files
light-heart-labs--dreamserver/ods/scripts/validate-manifest-schema.sh
T
wehub-resource-sync 9e8f1bbeed
Dashboard / frontend (push) Failing after 0s
Dashboard / api (push) Failing after 0s
Lint PowerShell / powershell-lint (ubuntu-latest) (push) Failing after 1s
Python Lint / Lint Python with Ruff (push) Failing after 1s
ShellCheck / Lint shell scripts (push) Failing after 1s
Matrix Smoke / linux-smoke (push) Failing after 1s
Matrix Smoke / distro: cachyos (push) Failing after 15s
Matrix Smoke / distro: linux-mint-21.3 (push) Failing after 15s
Matrix Smoke / distro: debian-12 (push) Failing after 5m21s
Matrix Smoke / distro: fedora-41 (push) Failing after 4m56s
Matrix Smoke / distro: ubuntu-24.04 (push) Failing after 2m13s
Matrix Smoke / distro: rocky-9 (push) Failing after 10m39s
Matrix Smoke / distro: manjaro (push) Failing after 12m11s
Matrix Smoke / distro: opensuse-tw (push) Failing after 11m53s
Matrix Smoke / distro: archlinux (push) Failing after 20m3s
Matrix Smoke / distro: ubuntu-22.04 (push) Failing after 13m49s
Validate .env Schema / tier-1-env-validation (push) Successful in 52s
Validate .env Schema / tier-2-env-validation (push) Successful in 44s
Validate .env Schema / tier-3-env-validation (push) Successful in 52s
Validate .env Schema / tier-4-env-validation (push) Successful in 51s
Validate Extensions Catalog / Check catalog is up-to-date (push) Failing after 9m47s
Secret Scan / Scan for secrets (push) Failing after 21m4s
Validate Docker Compose / Validate Docker Compose files (push) Has been cancelled
Python Type Check / Type check with mypy (push) Has been cancelled
Validate .env Schema / tier-0-env-validation (push) Has been cancelled
Test Linux / integration-smoke (push) Has been cancelled
Lint PowerShell / powershell-lint (windows-latest) (push) Has been cancelled
Matrix Smoke / macos-smoke (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:31:33 +08:00

198 lines
6.4 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# validate-manifest-schema.sh - Comprehensive manifest schema validator
# Part of: scripts/
# Purpose: Validate extension manifests against schema requirements
#
# Usage: ./validate-manifest-schema.sh [--strict] [--verbose]
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
EXTENSIONS_DIR="${SCRIPT_DIR}/../extensions/services"
STRICT_MODE=false
VERBOSE=false
ERRORS=0
WARNINGS=0
# Colors
RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m'
usage() {
cat << EOF
Extension Manifest Schema Validator
Usage: $(basename "$0") [OPTIONS]
OPTIONS:
-h, --help Show this help message
-s, --strict Treat warnings as errors
-v, --verbose Show detailed validation output
DESCRIPTION:
Validates all extension manifest.yaml files against schema requirements.
Checks required fields, types, formats, and logical consistency.
EXAMPLES:
$(basename "$0") # Validate all manifests
$(basename "$0") --strict # Fail on warnings
$(basename "$0") --verbose # Show all checks
EOF
}
error() {
echo -e "${RED}✗ ERROR:${NC} $*" >&2
((ERRORS++)) || true
}
warn() {
echo -e "${YELLOW}⚠ WARNING:${NC} $*" >&2
((WARNINGS++)) || true
}
info() {
[[ "$VERBOSE" == "true" ]] && echo -e "${BLUE}${NC} $*"
}
success() {
[[ "$VERBOSE" == "true" ]] && echo -e "${GREEN}${NC} $*"
}
# Validate a single manifest
validate_manifest() {
local manifest_path="$1"
local service_name
service_name=$(basename "$(dirname "$manifest_path")")
info "Validating: $service_name"
# Check YAML syntax
if ! python3 -c "import yaml; yaml.safe_load(open('$manifest_path'))" 2>/dev/null; then
error "$service_name: Invalid YAML syntax"
return 1
fi
# Comprehensive validation
python3 - "$manifest_path" "$service_name" "$VERBOSE" <<'PYEOF'
import yaml, sys, re, os
manifest_path, service_name, verbose = sys.argv[1:4]
errors, warnings = [], []
def error(msg): errors.append(msg); print(f"ERROR: {service_name}: {msg}", file=sys.stderr)
def warn(msg): warnings.append(msg); print(f"WARNING: {service_name}: {msg}", file=sys.stderr)
def info(msg): verbose == "true" and print(f"INFO: {service_name}: {msg}")
try:
manifest = yaml.safe_load(open(manifest_path))
if not isinstance(manifest, dict): error("Not a valid YAML mapping"); sys.exit(1)
# schema_version
if manifest.get("schema_version") != "ods.services.v1":
error(f"Invalid schema_version: {manifest.get('schema_version')}")
else: info("schema_version: OK")
service = manifest.get("service", {})
if not isinstance(service, dict): error("Missing/invalid 'service' section"); sys.exit(1)
# Required fields
for field, typ in {"id": str, "name": str, "port": int, "health": str, "type": str, "category": str}.items():
val = service.get(field)
if val is None: error(f"Missing service.{field}")
elif not isinstance(val, typ): error(f"Invalid type for service.{field}")
else: info(f"service.{field}: OK")
# Validate formats
if service.get("id") and not re.match(r'^[a-z0-9_-]+$', service["id"]):
error(f"Invalid service.id format: {service['id']}")
if service.get("category") not in ["core", "recommended", "optional", None]:
error(f"Invalid category: {service.get('category')}")
if service.get("type") not in ["docker", "native", "external", None]:
error(f"Invalid type: {service.get('type')}")
port = service.get("port", 0)
if isinstance(port, int) and not (0 <= port <= 65535):
error(f"Invalid port: {port}")
if service.get("health") and not service["health"].startswith("/"):
warn(f"health should start with '/': {service['health']}")
# Validate lists
for alias in service.get("aliases", []):
if not re.match(r'^[a-z0-9_-]+$', str(alias)):
error(f"Invalid alias: {alias}")
for dep in service.get("depends_on", []):
if not re.match(r'^[a-z0-9_-]+$', str(dep)):
error(f"Invalid dependency: {dep}")
for backend in service.get("gpu_backends", []):
if backend not in ["amd", "nvidia", "apple", "cpu", "all"]:
error(f"Invalid gpu_backend: {backend}")
# Check compose_file exists
if service.get("compose_file"):
compose_path = os.path.join(os.path.dirname(manifest_path), service["compose_file"])
if not os.path.exists(compose_path):
warn(f"compose_file not found: {service['compose_file']}")
sys.exit(1 if errors else (2 if warnings else 0))
except Exception as e:
print(f"ERROR: {service_name}: {e}", file=sys.stderr); sys.exit(1)
PYEOF
case $? in
0) success "$service_name: Valid"; return 0 ;;
1) ((ERRORS++)) || true; return 1 ;;
2) ((WARNINGS++)) || true; return 0 ;;
esac
}
# Parse args
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help) usage; exit 0 ;;
-s|--strict) STRICT_MODE=true; shift ;;
-v|--verbose) VERBOSE=true; shift ;;
*) echo "Unknown: $1" >&2; usage; exit 2 ;;
esac
done
# Main
echo "Validating manifests in: $EXTENSIONS_DIR"
echo ""
[[ ! -d "$EXTENSIONS_DIR" ]] && { echo -e "${RED}ERROR:${NC} Not found: $EXTENSIONS_DIR" >&2; exit 1; }
TOTAL=0 VALID=0
for dir in "$EXTENSIONS_DIR"/*/; do
[[ ! -d "$dir" ]] && continue
manifest=""
for name in manifest.yaml manifest.yml; do
[[ -f "$dir/$name" ]] && manifest="$dir/$name" && break
done
[[ -z "$manifest" ]] && { warn "$(basename "$dir"): No manifest"; continue; }
((TOTAL++)) || true
validate_manifest "$manifest" && { ((VALID++)) || true; }
done
# Summary
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Summary: $TOTAL total, $VALID valid, $ERRORS errors, $WARNINGS warnings"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
if [[ $ERRORS -gt 0 ]]; then
echo -e "${RED}✗ FAILED${NC} ($ERRORS errors)"; exit 1
elif [[ $WARNINGS -gt 0 && "$STRICT_MODE" == "true" ]]; then
echo -e "${YELLOW}✗ FAILED${NC} ($WARNINGS warnings in strict mode)"; exit 1
elif [[ $WARNINGS -gt 0 ]]; then
echo -e "${YELLOW}⚠ Passed with warnings${NC}"; exit 0
else
echo -e "${GREEN}✓ All valid${NC}"; exit 0
fi