178 lines
5.2 KiB
Bash
Executable File
178 lines
5.2 KiB
Bash
Executable File
#!/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 "$@"
|