Sanitized mirror from private repository - 2026-03-28 12:26:38 UTC
This commit is contained in:
177
scripts/validate-compose.sh
Executable file
177
scripts/validate-compose.sh
Executable file
@@ -0,0 +1,177 @@
|
||||
#!/bin/bash
|
||||
# Docker Compose Validation Script
|
||||
# Validates Docker Compose files before commit to prevent broken deployments
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Function to log messages
|
||||
log_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Check if Docker is available
|
||||
if ! command -v docker &> /dev/null; then
|
||||
log_warn "Docker not found. Skipping Docker Compose validation."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if docker-compose is available
|
||||
if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then
|
||||
log_warn "Docker Compose not found. Skipping validation."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Determine docker-compose command
|
||||
if command -v docker-compose &> /dev/null; then
|
||||
COMPOSE_CMD="docker-compose"
|
||||
else
|
||||
COMPOSE_CMD="docker compose"
|
||||
fi
|
||||
|
||||
# Validation function
|
||||
validate_compose_file() {
|
||||
local file="$1"
|
||||
local filename=$(basename "$file")
|
||||
|
||||
# Skip non-Docker Compose files
|
||||
if [[ "$file" == *".pre-commit-config.yaml" ]] || \
|
||||
[[ "$file" == *".yamllint" ]] || \
|
||||
[[ "$file" == *".gitea/workflows/"* ]] || \
|
||||
[[ "$file" == *"secret_key.yaml" ]] || \
|
||||
[[ "$file" == *"config.yml" ]] || \
|
||||
[[ "$file" == *"snmp.yml" ]] || \
|
||||
[[ "$file" == *"homeserver.yaml" ]]; then
|
||||
log_info "Skipping non-Docker Compose file: $file"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Skip files that don't have a 'services:' block (not Docker Compose files)
|
||||
if ! grep -q "^services:" "$file" 2>/dev/null; then
|
||||
log_info "Skipping non-Docker Compose file: $file"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Skip compose files with env_file references to files that don't exist locally
|
||||
if grep -q "env_file:" "$file" 2>/dev/null; then
|
||||
local compose_dir
|
||||
compose_dir=$(dirname "$file")
|
||||
local missing_env=0
|
||||
while IFS= read -r env_line; do
|
||||
local env_file
|
||||
env_file=$(echo "$env_line" | sed 's/.*-\s*//' | tr -d ' "')
|
||||
if [[ -n "$env_file" ]] && [[ "$env_file" != "~" ]] && \
|
||||
[[ ! -f "$compose_dir/$env_file" ]]; then
|
||||
missing_env=1
|
||||
break
|
||||
fi
|
||||
done < <(grep -A1 "env_file:" "$file" | grep "^.*-")
|
||||
if [[ $missing_env -eq 1 ]]; then
|
||||
log_warn "$file: Skipping validation - missing env_file dependencies"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
log_info "Validating $file"
|
||||
|
||||
# Check if file exists and is readable
|
||||
if [[ ! -r "$file" ]]; then
|
||||
log_error "Cannot read file: $file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Skip if not a compose file
|
||||
if [[ ! "$filename" =~ \.(yml|yaml)$ ]]; then
|
||||
log_info "Skipping non-YAML file: $file"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Skip certain directories and files
|
||||
if [[ "$file" =~ ^(archive/|ansible/|docs/|\.git/) ]]; then
|
||||
log_info "Skipping excluded path: $file"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Validate Docker Compose syntax
|
||||
if ! $COMPOSE_CMD -f "$file" config > /dev/null 2>&1; then
|
||||
log_error "Docker Compose validation failed for: $file"
|
||||
log_error "Run '$COMPOSE_CMD -f $file config' to see detailed errors"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check for common issues
|
||||
local warnings=0
|
||||
|
||||
# Check for missing version (Docker Compose v2 doesn't require it, but good practice)
|
||||
if ! grep -q "^version:" "$file" 2>/dev/null; then
|
||||
log_warn "$file: Consider adding 'version' field for clarity"
|
||||
((warnings++))
|
||||
fi
|
||||
|
||||
# Check for hardcoded localhost references (should use service names)
|
||||
if grep -q "localhost\|127\.0\.0\.1" "$file" 2>/dev/null; then
|
||||
log_warn "$file: Found localhost references - consider using service names"
|
||||
((warnings++))
|
||||
fi
|
||||
|
||||
# Check for missing restart policies on long-running services
|
||||
if grep -q "image:" "$file" && ! grep -q "restart:" "$file" 2>/dev/null; then
|
||||
log_warn "$file: Consider adding restart policy for production services"
|
||||
((warnings++))
|
||||
fi
|
||||
|
||||
if [[ $warnings -eq 0 ]]; then
|
||||
log_info "✓ $file passed validation"
|
||||
else
|
||||
log_info "✓ $file passed validation with $warnings warnings"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Main execution
|
||||
main() {
|
||||
local exit_code=0
|
||||
local files_processed=0
|
||||
|
||||
# If no arguments provided, validate all YAML files
|
||||
if [[ $# -eq 0 ]]; then
|
||||
log_info "No files specified, validating all Docker Compose files..."
|
||||
while IFS= read -r -d '' file; do
|
||||
((files_processed++))
|
||||
if ! validate_compose_file "$file"; then
|
||||
exit_code=1
|
||||
fi
|
||||
done < <(find . -name "*.yml" -o -name "*.yaml" -print0 | grep -zv -E '^(archive/|ansible/|docs/|\.git/)')
|
||||
else
|
||||
# Validate specified files
|
||||
for file in "$@"; do
|
||||
((files_processed++))
|
||||
if ! validate_compose_file "$file"; then
|
||||
exit_code=1
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ $exit_code -eq 0 ]]; then
|
||||
log_info "All $files_processed files passed validation!"
|
||||
else
|
||||
log_error "Some files failed validation. Please fix the errors before committing."
|
||||
fi
|
||||
|
||||
exit $exit_code
|
||||
}
|
||||
|
||||
# Run main function with all arguments
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user