first commit
This commit is contained in:
Executable
+473
@@ -0,0 +1,473 @@
|
||||
#!/bin/bash
|
||||
|
||||
# TankStopp Dockerfile Validation Script
|
||||
# This script validates Dockerfile syntax and best practices without requiring Docker daemon
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Configuration
|
||||
DOCKERFILE_PATH="Dockerfile"
|
||||
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
VALIDATION_ERRORS=0
|
||||
VALIDATION_WARNINGS=0
|
||||
|
||||
# Functions
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
((VALIDATION_WARNINGS++))
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
((VALIDATION_ERRORS++))
|
||||
}
|
||||
|
||||
check_dockerfile_exists() {
|
||||
log_info "Checking Dockerfile existence..."
|
||||
|
||||
if [ ! -f "$PROJECT_ROOT/$DOCKERFILE_PATH" ]; then
|
||||
log_error "Dockerfile not found at $PROJECT_ROOT/$DOCKERFILE_PATH"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_success "Dockerfile found"
|
||||
return 0
|
||||
}
|
||||
|
||||
validate_dockerfile_syntax() {
|
||||
log_info "Validating Dockerfile syntax..."
|
||||
|
||||
local dockerfile="$PROJECT_ROOT/$DOCKERFILE_PATH"
|
||||
|
||||
# Check for FROM instruction
|
||||
if ! grep -q "^FROM " "$dockerfile"; then
|
||||
log_error "No FROM instruction found"
|
||||
else
|
||||
log_success "FROM instruction found"
|
||||
fi
|
||||
|
||||
# Check for multi-stage build
|
||||
local from_count=$(grep -c "^FROM " "$dockerfile")
|
||||
if [ "$from_count" -gt 1 ]; then
|
||||
log_success "Multi-stage build detected ($from_count stages)"
|
||||
else
|
||||
log_warning "Single-stage build detected. Consider multi-stage for optimization"
|
||||
fi
|
||||
|
||||
# Check for WORKDIR
|
||||
if grep -q "^WORKDIR " "$dockerfile"; then
|
||||
log_success "WORKDIR instruction found"
|
||||
else
|
||||
log_warning "No WORKDIR instruction found"
|
||||
fi
|
||||
|
||||
# Check for USER instruction
|
||||
if grep -q "^USER " "$dockerfile"; then
|
||||
log_success "USER instruction found (security best practice)"
|
||||
else
|
||||
log_warning "No USER instruction found. Running as root is not recommended"
|
||||
fi
|
||||
|
||||
# Check for EXPOSE instruction
|
||||
if grep -q "^EXPOSE " "$dockerfile"; then
|
||||
log_success "EXPOSE instruction found"
|
||||
else
|
||||
log_warning "No EXPOSE instruction found"
|
||||
fi
|
||||
|
||||
# Check for HEALTHCHECK
|
||||
if grep -q "^HEALTHCHECK " "$dockerfile"; then
|
||||
log_success "HEALTHCHECK instruction found"
|
||||
else
|
||||
log_warning "No HEALTHCHECK instruction found"
|
||||
fi
|
||||
|
||||
# Check for VOLUME
|
||||
if grep -q "^VOLUME " "$dockerfile"; then
|
||||
log_success "VOLUME instruction found"
|
||||
else
|
||||
log_warning "No VOLUME instruction found"
|
||||
fi
|
||||
}
|
||||
|
||||
validate_base_images() {
|
||||
log_info "Validating base images..."
|
||||
|
||||
local dockerfile="$PROJECT_ROOT/$DOCKERFILE_PATH"
|
||||
|
||||
# Extract base images
|
||||
local base_images=$(grep "^FROM " "$dockerfile" | awk '{print $2}')
|
||||
|
||||
for image in $base_images; do
|
||||
if [[ "$image" == *":latest" ]]; then
|
||||
log_warning "Base image '$image' uses 'latest' tag. Consider pinning to specific version"
|
||||
elif [[ "$image" =~ :[0-9]+\.[0-9]+ ]]; then
|
||||
log_success "Base image '$image' uses versioned tag"
|
||||
else
|
||||
log_info "Base image: $image"
|
||||
fi
|
||||
|
||||
# Check for official images
|
||||
if [[ "$image" =~ ^(alpine|golang|node|nginx|redis|postgres|mysql):.* ]]; then
|
||||
log_success "Using official base image: $image"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
validate_security_practices() {
|
||||
log_info "Validating security practices..."
|
||||
|
||||
local dockerfile="$PROJECT_ROOT/$DOCKERFILE_PATH"
|
||||
|
||||
# Check if running as root
|
||||
if grep -q "USER root" "$dockerfile"; then
|
||||
log_error "Explicitly running as root user"
|
||||
fi
|
||||
|
||||
# Check for package manager cache cleanup
|
||||
if grep -q "rm -rf /var/cache/apk/\*\|apt-get clean\|yum clean" "$dockerfile"; then
|
||||
log_success "Package manager cache cleanup found"
|
||||
else
|
||||
log_warning "Consider cleaning package manager cache to reduce image size"
|
||||
fi
|
||||
|
||||
# Check for unnecessary packages
|
||||
if grep -q "curl\|wget" "$dockerfile"; then
|
||||
local curl_line=$(grep -n "curl\|wget" "$dockerfile" | head -1)
|
||||
log_info "Network tools found: $curl_line"
|
||||
fi
|
||||
|
||||
# Check for secrets in Dockerfile
|
||||
if grep -qi "password\|secret\|key\|token" "$dockerfile"; then
|
||||
log_warning "Potential secrets found in Dockerfile. Ensure no hardcoded credentials"
|
||||
fi
|
||||
}
|
||||
|
||||
validate_build_optimization() {
|
||||
log_info "Validating build optimization..."
|
||||
|
||||
local dockerfile="$PROJECT_ROOT/$DOCKERFILE_PATH"
|
||||
|
||||
# Check for layer optimization
|
||||
local run_count=$(grep -c "^RUN " "$dockerfile")
|
||||
if [ "$run_count" -gt 10 ]; then
|
||||
log_warning "Many RUN instructions ($run_count). Consider combining for fewer layers"
|
||||
else
|
||||
log_success "Reasonable number of RUN instructions ($run_count)"
|
||||
fi
|
||||
|
||||
# Check for COPY vs ADD
|
||||
if grep -q "^ADD " "$dockerfile"; then
|
||||
log_warning "ADD instruction found. Consider using COPY unless URL/tar extraction is needed"
|
||||
fi
|
||||
|
||||
# Check for .dockerignore
|
||||
if [ -f "$PROJECT_ROOT/.dockerignore" ]; then
|
||||
log_success ".dockerignore file found"
|
||||
else
|
||||
log_warning ".dockerignore file not found. Consider adding to reduce build context"
|
||||
fi
|
||||
|
||||
# Check for build args
|
||||
if grep -q "^ARG " "$dockerfile"; then
|
||||
log_success "Build arguments found for flexibility"
|
||||
fi
|
||||
}
|
||||
|
||||
validate_required_files() {
|
||||
log_info "Validating required files..."
|
||||
|
||||
# Check for Go module files
|
||||
if [ -f "$PROJECT_ROOT/go.mod" ]; then
|
||||
log_success "go.mod found"
|
||||
else
|
||||
log_error "go.mod not found"
|
||||
fi
|
||||
|
||||
if [ -f "$PROJECT_ROOT/go.sum" ]; then
|
||||
log_success "go.sum found"
|
||||
else
|
||||
log_warning "go.sum not found"
|
||||
fi
|
||||
|
||||
# Check for main.go or cmd directory
|
||||
if [ -f "$PROJECT_ROOT/main.go" ] || [ -d "$PROJECT_ROOT/cmd" ]; then
|
||||
log_success "Main Go file or cmd directory found"
|
||||
else
|
||||
log_error "No main.go or cmd directory found"
|
||||
fi
|
||||
|
||||
# Check for static files
|
||||
if [ -d "$PROJECT_ROOT/static" ]; then
|
||||
log_success "Static directory found"
|
||||
else
|
||||
log_warning "Static directory not found"
|
||||
fi
|
||||
|
||||
# Check for configuration files
|
||||
if ls "$PROJECT_ROOT"/config*.yaml >/dev/null 2>&1; then
|
||||
log_success "Configuration files found"
|
||||
else
|
||||
log_warning "No configuration files found"
|
||||
fi
|
||||
}
|
||||
|
||||
validate_environment_variables() {
|
||||
log_info "Validating environment variables..."
|
||||
|
||||
local dockerfile="$PROJECT_ROOT/$DOCKERFILE_PATH"
|
||||
|
||||
# Check for ENV instructions
|
||||
local env_count=$(grep -c "^ENV " "$dockerfile")
|
||||
if [ "$env_count" -gt 0 ]; then
|
||||
log_success "Environment variables defined ($env_count)"
|
||||
|
||||
# Show environment variables
|
||||
grep "^ENV " "$dockerfile" | while read line; do
|
||||
log_info " $line"
|
||||
done
|
||||
else
|
||||
log_info "No ENV instructions found"
|
||||
fi
|
||||
|
||||
# Check for common application variables
|
||||
if grep -q "TANKSTOPP_" "$dockerfile"; then
|
||||
log_success "Application-specific environment variables found"
|
||||
fi
|
||||
}
|
||||
|
||||
validate_ports_and_volumes() {
|
||||
log_info "Validating ports and volumes..."
|
||||
|
||||
local dockerfile="$PROJECT_ROOT/$DOCKERFILE_PATH"
|
||||
|
||||
# Check exposed ports
|
||||
if grep -q "^EXPOSE " "$dockerfile"; then
|
||||
local ports=$(grep "^EXPOSE " "$dockerfile" | awk '{print $2}')
|
||||
log_success "Exposed ports: $ports"
|
||||
fi
|
||||
|
||||
# Check volumes
|
||||
if grep -q "^VOLUME " "$dockerfile"; then
|
||||
local volumes=$(grep "^VOLUME " "$dockerfile" | sed 's/VOLUME //')
|
||||
log_success "Volumes defined: $volumes"
|
||||
fi
|
||||
}
|
||||
|
||||
validate_labels_and_metadata() {
|
||||
log_info "Validating labels and metadata..."
|
||||
|
||||
local dockerfile="$PROJECT_ROOT/$DOCKERFILE_PATH"
|
||||
|
||||
# Check for labels
|
||||
if grep -q "^LABEL " "$dockerfile"; then
|
||||
log_success "Labels found"
|
||||
grep "^LABEL " "$dockerfile" | while read line; do
|
||||
log_info " $line"
|
||||
done
|
||||
else
|
||||
log_warning "No labels found. Consider adding metadata labels"
|
||||
fi
|
||||
}
|
||||
|
||||
validate_entrypoint_and_cmd() {
|
||||
log_info "Validating entrypoint and command..."
|
||||
|
||||
local dockerfile="$PROJECT_ROOT/$DOCKERFILE_PATH"
|
||||
|
||||
# Check for ENTRYPOINT
|
||||
if grep -q "^ENTRYPOINT " "$dockerfile"; then
|
||||
log_success "ENTRYPOINT instruction found"
|
||||
fi
|
||||
|
||||
# Check for CMD
|
||||
if grep -q "^CMD " "$dockerfile"; then
|
||||
log_success "CMD instruction found"
|
||||
local cmd=$(grep "^CMD " "$dockerfile" | tail -1)
|
||||
log_info " $cmd"
|
||||
else
|
||||
log_warning "No CMD instruction found"
|
||||
fi
|
||||
}
|
||||
|
||||
check_docker_compose_files() {
|
||||
log_info "Checking Docker Compose files..."
|
||||
|
||||
if [ -f "$PROJECT_ROOT/docker-compose.yml" ]; then
|
||||
log_success "docker-compose.yml found"
|
||||
else
|
||||
log_warning "docker-compose.yml not found"
|
||||
fi
|
||||
|
||||
if [ -f "$PROJECT_ROOT/docker-compose.prod.yml" ]; then
|
||||
log_success "docker-compose.prod.yml found"
|
||||
fi
|
||||
|
||||
if [ -f "$PROJECT_ROOT/docker-compose.override.yml" ]; then
|
||||
log_info "docker-compose.override.yml found"
|
||||
fi
|
||||
}
|
||||
|
||||
validate_build_scripts() {
|
||||
log_info "Checking build scripts..."
|
||||
|
||||
if [ -f "$PROJECT_ROOT/scripts/docker/build.sh" ]; then
|
||||
log_success "Docker build script found"
|
||||
if [ -x "$PROJECT_ROOT/scripts/docker/build.sh" ]; then
|
||||
log_success "Build script is executable"
|
||||
else
|
||||
log_warning "Build script is not executable"
|
||||
fi
|
||||
else
|
||||
log_warning "Docker build script not found"
|
||||
fi
|
||||
|
||||
if [ -f "$PROJECT_ROOT/scripts/docker/deploy.sh" ]; then
|
||||
log_success "Docker deploy script found"
|
||||
if [ -x "$PROJECT_ROOT/scripts/docker/deploy.sh" ]; then
|
||||
log_success "Deploy script is executable"
|
||||
else
|
||||
log_warning "Deploy script is not executable"
|
||||
fi
|
||||
else
|
||||
log_warning "Docker deploy script not found"
|
||||
fi
|
||||
}
|
||||
|
||||
generate_recommendations() {
|
||||
log_info "Generating recommendations..."
|
||||
|
||||
echo ""
|
||||
echo "=== RECOMMENDATIONS ==="
|
||||
|
||||
if [ "$VALIDATION_ERRORS" -eq 0 ] && [ "$VALIDATION_WARNINGS" -eq 0 ]; then
|
||||
log_success "Dockerfile validation passed with no issues!"
|
||||
else
|
||||
echo ""
|
||||
echo "Consider the following improvements:"
|
||||
echo ""
|
||||
|
||||
if grep -q "USER root" "$PROJECT_ROOT/$DOCKERFILE_PATH" 2>/dev/null; then
|
||||
echo "• Create and use a non-root user for security"
|
||||
fi
|
||||
|
||||
if ! grep -q "HEALTHCHECK" "$PROJECT_ROOT/$DOCKERFILE_PATH" 2>/dev/null; then
|
||||
echo "• Add HEALTHCHECK instruction for container monitoring"
|
||||
fi
|
||||
|
||||
if ! grep -q "LABEL" "$PROJECT_ROOT/$DOCKERFILE_PATH" 2>/dev/null; then
|
||||
echo "• Add metadata labels (version, maintainer, description)"
|
||||
fi
|
||||
|
||||
if [ ! -f "$PROJECT_ROOT/.dockerignore" ]; then
|
||||
echo "• Create .dockerignore to optimize build context"
|
||||
fi
|
||||
|
||||
if ! grep -q "VOLUME" "$PROJECT_ROOT/$DOCKERFILE_PATH" 2>/dev/null; then
|
||||
echo "• Consider adding VOLUME for data persistence"
|
||||
fi
|
||||
|
||||
echo "• Pin base image versions for reproducible builds"
|
||||
echo "• Minimize the number of layers by combining RUN commands"
|
||||
echo "• Remove package manager caches to reduce image size"
|
||||
echo "• Use multi-stage builds to reduce final image size"
|
||||
fi
|
||||
}
|
||||
|
||||
show_summary() {
|
||||
echo ""
|
||||
echo "=== VALIDATION SUMMARY ==="
|
||||
echo "Dockerfile: $PROJECT_ROOT/$DOCKERFILE_PATH"
|
||||
echo "Errors: $VALIDATION_ERRORS"
|
||||
echo "Warnings: $VALIDATION_WARNINGS"
|
||||
|
||||
if [ "$VALIDATION_ERRORS" -eq 0 ]; then
|
||||
log_success "Dockerfile validation completed successfully!"
|
||||
echo "Your Dockerfile is ready for building."
|
||||
else
|
||||
log_error "Dockerfile validation failed with $VALIDATION_ERRORS errors"
|
||||
echo "Please fix the errors before building the Docker image."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
echo "TankStopp Dockerfile Validation"
|
||||
echo "==============================="
|
||||
echo ""
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
# Run all validations
|
||||
check_dockerfile_exists || exit 1
|
||||
validate_dockerfile_syntax
|
||||
validate_base_images
|
||||
validate_security_practices
|
||||
validate_build_optimization
|
||||
validate_required_files
|
||||
validate_environment_variables
|
||||
validate_ports_and_volumes
|
||||
validate_labels_and_metadata
|
||||
validate_entrypoint_and_cmd
|
||||
check_docker_compose_files
|
||||
validate_build_scripts
|
||||
|
||||
# Generate recommendations and summary
|
||||
generate_recommendations
|
||||
show_summary
|
||||
}
|
||||
|
||||
# Show help if requested
|
||||
if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
|
||||
cat << EOF
|
||||
TankStopp Dockerfile Validation Script
|
||||
|
||||
Usage: $0 [OPTIONS]
|
||||
|
||||
This script validates your Dockerfile against best practices and security guidelines.
|
||||
It checks syntax, security practices, optimization, and required files.
|
||||
|
||||
OPTIONS:
|
||||
-h, --help Show this help message
|
||||
|
||||
CHECKS PERFORMED:
|
||||
• Dockerfile syntax validation
|
||||
• Base image version pinning
|
||||
• Security best practices
|
||||
• Build optimization
|
||||
• Required files presence
|
||||
• Environment variables
|
||||
• Port and volume configuration
|
||||
• Labels and metadata
|
||||
• Entrypoint and command setup
|
||||
• Docker Compose files
|
||||
• Build scripts
|
||||
|
||||
EXAMPLES:
|
||||
# Run validation
|
||||
$0
|
||||
|
||||
# Show help
|
||||
$0 --help
|
||||
|
||||
EOF
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Run main function
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user