first commit
This commit is contained in:
Executable
+200
@@ -0,0 +1,200 @@
|
||||
#!/bin/bash
|
||||
|
||||
# TankStopp Build Script
|
||||
# This script handles templ generation and application building
|
||||
|
||||
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
|
||||
|
||||
# Function to print colored output
|
||||
print_status() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Function to check if templ is installed
|
||||
check_templ() {
|
||||
if ! command -v templ &> /dev/null; then
|
||||
print_error "templ CLI is not installed"
|
||||
print_status "Installing templ..."
|
||||
go install github.com/a-h/templ/cmd/templ@latest
|
||||
if [ $? -eq 0 ]; then
|
||||
print_success "templ installed successfully"
|
||||
else
|
||||
print_error "Failed to install templ"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
print_success "templ CLI is available"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to format templ files
|
||||
format_templ() {
|
||||
print_status "Formatting templ files..."
|
||||
templ fmt ./internal/views/
|
||||
if [ $? -eq 0 ]; then
|
||||
print_success "Templ files formatted successfully"
|
||||
else
|
||||
print_error "Failed to format templ files"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to generate Go code from templ files
|
||||
generate_templ() {
|
||||
print_status "Generating Go code from templ files..."
|
||||
templ generate ./internal/views/
|
||||
if [ $? -eq 0 ]; then
|
||||
print_success "Go code generated successfully"
|
||||
else
|
||||
print_error "Failed to generate Go code from templ files"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to build the application
|
||||
build_app() {
|
||||
print_status "Building application..."
|
||||
go build -o tankstopp ./cmd/main.go
|
||||
if [ $? -eq 0 ]; then
|
||||
print_success "Application built successfully"
|
||||
else
|
||||
print_error "Failed to build application"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to run tests
|
||||
run_tests() {
|
||||
print_status "Running tests..."
|
||||
go test ./...
|
||||
if [ $? -eq 0 ]; then
|
||||
print_success "All tests passed"
|
||||
else
|
||||
print_error "Some tests failed"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to clean generated files
|
||||
clean() {
|
||||
print_status "Cleaning generated files..."
|
||||
find ./internal/views -name "*_templ.go" -delete
|
||||
rm -f tankstopp
|
||||
print_success "Cleaned generated files"
|
||||
}
|
||||
|
||||
# Function to watch for changes (development mode)
|
||||
watch() {
|
||||
print_status "Starting development watch mode..."
|
||||
print_warning "This requires 'entr' to be installed (brew install entr / apt-get install entr)"
|
||||
|
||||
if ! command -v entr &> /dev/null; then
|
||||
print_error "entr is not installed. Install it with: brew install entr (macOS) or apt-get install entr (Linux)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_status "Watching for changes in templ files..."
|
||||
find ./internal/views -name "*.templ" | entr -r sh -c 'echo "Changes detected, rebuilding..." && ./scripts/build.sh dev'
|
||||
}
|
||||
|
||||
# Function to show help
|
||||
show_help() {
|
||||
echo "TankStopp Build Script"
|
||||
echo ""
|
||||
echo "Usage: $0 [COMMAND]"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " dev - Development build (format, generate, build)"
|
||||
echo " prod - Production build (format, generate, test, build)"
|
||||
echo " generate - Generate Go code from templ files only"
|
||||
echo " format - Format templ files only"
|
||||
echo " clean - Clean generated files"
|
||||
echo " test - Run tests only"
|
||||
echo " watch - Watch for changes and rebuild (requires entr)"
|
||||
echo " help - Show this help message"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 dev # Quick development build"
|
||||
echo " $0 prod # Production build with tests"
|
||||
echo " $0 watch # Watch mode for development"
|
||||
}
|
||||
|
||||
# Main script logic
|
||||
main() {
|
||||
case "${1:-dev}" in
|
||||
"dev")
|
||||
print_status "Starting development build..."
|
||||
check_templ
|
||||
format_templ
|
||||
generate_templ
|
||||
build_app
|
||||
print_success "Development build completed!"
|
||||
;;
|
||||
"prod")
|
||||
print_status "Starting production build..."
|
||||
check_templ
|
||||
format_templ
|
||||
generate_templ
|
||||
run_tests
|
||||
build_app
|
||||
print_success "Production build completed!"
|
||||
;;
|
||||
"generate")
|
||||
print_status "Generating templ files..."
|
||||
check_templ
|
||||
generate_templ
|
||||
print_success "Generation completed!"
|
||||
;;
|
||||
"format")
|
||||
print_status "Formatting templ files..."
|
||||
check_templ
|
||||
format_templ
|
||||
print_success "Formatting completed!"
|
||||
;;
|
||||
"clean")
|
||||
clean
|
||||
;;
|
||||
"test")
|
||||
run_tests
|
||||
;;
|
||||
"watch")
|
||||
watch
|
||||
;;
|
||||
"help"|"-h"|"--help")
|
||||
show_help
|
||||
;;
|
||||
*)
|
||||
print_error "Unknown command: $1"
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Check if we're in the right directory
|
||||
if [ ! -f "go.mod" ]; then
|
||||
print_error "This script must be run from the project root directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Run main function with all arguments
|
||||
main "$@"
|
||||
Executable
+309
@@ -0,0 +1,309 @@
|
||||
#!/bin/bash
|
||||
|
||||
# TankStopp Docker Build Script
|
||||
# This script builds Docker images for the TankStopp application
|
||||
|
||||
set -e # Exit on any error
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Default values
|
||||
IMAGE_NAME="tankstopp"
|
||||
TAG="latest"
|
||||
DOCKERFILE="Dockerfile"
|
||||
CONTEXT="."
|
||||
BUILD_ARGS=""
|
||||
PLATFORM=""
|
||||
PUSH=false
|
||||
NO_CACHE=false
|
||||
QUIET=false
|
||||
ENVIRONMENT="production"
|
||||
|
||||
# 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"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
show_help() {
|
||||
cat << EOF
|
||||
TankStopp Docker Build Script
|
||||
|
||||
Usage: $0 [OPTIONS]
|
||||
|
||||
OPTIONS:
|
||||
-h, --help Show this help message
|
||||
-t, --tag TAG Image tag (default: latest)
|
||||
-e, --env ENV Environment: development|production (default: production)
|
||||
-f, --file FILE Dockerfile path (default: Dockerfile)
|
||||
-c, --context PATH Build context path (default: .)
|
||||
-p, --platform ARCH Target platform (e.g., linux/amd64,linux/arm64)
|
||||
--push Push image to registry after build
|
||||
--no-cache Build without using cache
|
||||
--quiet Suppress build output
|
||||
--build-arg ARG=VALUE Pass build arguments
|
||||
--clean Clean up old images before building
|
||||
|
||||
EXAMPLES:
|
||||
# Build production image
|
||||
$0 --tag v1.0.0 --env production
|
||||
|
||||
# Build development image
|
||||
$0 --tag dev --env development
|
||||
|
||||
# Build multi-platform image
|
||||
$0 --tag latest --platform linux/amd64,linux/arm64
|
||||
|
||||
# Build and push to registry
|
||||
$0 --tag v1.0.0 --push
|
||||
|
||||
# Build with custom build args
|
||||
$0 --build-arg VERSION=1.0.0 --build-arg BUILD_DATE=\$(date -u +'%Y-%m-%dT%H:%M:%SZ')
|
||||
|
||||
ENVIRONMENT CONFIGURATIONS:
|
||||
production - Optimized for production deployment
|
||||
development - Includes development tools and debugging
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
check_requirements() {
|
||||
log_info "Checking requirements..."
|
||||
|
||||
if ! command -v docker &> /dev/null; then
|
||||
log_error "Docker is not installed or not in PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! docker info &> /dev/null; then
|
||||
log_error "Docker daemon is not running"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$DOCKERFILE" ]; then
|
||||
log_error "Dockerfile not found: $DOCKERFILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$CONTEXT" ]; then
|
||||
log_error "Build context directory not found: $CONTEXT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_success "Requirements check passed"
|
||||
}
|
||||
|
||||
clean_old_images() {
|
||||
log_info "Cleaning up old images..."
|
||||
|
||||
# Remove old tankstopp images (keep latest 3)
|
||||
OLD_IMAGES=$(docker images "$IMAGE_NAME" --format "{{.Repository}}:{{.Tag}}" | tail -n +4)
|
||||
if [ -n "$OLD_IMAGES" ]; then
|
||||
echo "$OLD_IMAGES" | xargs docker rmi -f 2>/dev/null || true
|
||||
log_success "Cleaned up old images"
|
||||
else
|
||||
log_info "No old images to clean"
|
||||
fi
|
||||
|
||||
# Remove dangling images
|
||||
DANGLING=$(docker images -f "dangling=true" -q)
|
||||
if [ -n "$DANGLING" ]; then
|
||||
echo "$DANGLING" | xargs docker rmi -f 2>/dev/null || true
|
||||
log_success "Removed dangling images"
|
||||
fi
|
||||
}
|
||||
|
||||
prepare_build_args() {
|
||||
log_info "Preparing build arguments..."
|
||||
|
||||
# Add environment-specific build args
|
||||
case "$ENVIRONMENT" in
|
||||
"development")
|
||||
BUILD_ARGS="$BUILD_ARGS --build-arg APP_ENV=development"
|
||||
BUILD_ARGS="$BUILD_ARGS --build-arg DEBUG=true"
|
||||
;;
|
||||
"production")
|
||||
BUILD_ARGS="$BUILD_ARGS --build-arg APP_ENV=production"
|
||||
BUILD_ARGS="$BUILD_ARGS --build-arg DEBUG=false"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Add common build args
|
||||
BUILD_ARGS="$BUILD_ARGS --build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')"
|
||||
BUILD_ARGS="$BUILD_ARGS --build-arg VCS_REF=$(git rev-parse --short HEAD 2>/dev/null || echo 'unknown')"
|
||||
BUILD_ARGS="$BUILD_ARGS --build-arg VERSION=$TAG"
|
||||
|
||||
log_success "Build arguments prepared"
|
||||
}
|
||||
|
||||
build_image() {
|
||||
log_info "Building Docker image..."
|
||||
log_info "Image: $IMAGE_NAME:$TAG"
|
||||
log_info "Environment: $ENVIRONMENT"
|
||||
log_info "Context: $CONTEXT"
|
||||
log_info "Dockerfile: $DOCKERFILE"
|
||||
|
||||
# Construct docker build command
|
||||
DOCKER_CMD="docker build"
|
||||
|
||||
# Add platform if specified
|
||||
if [ -n "$PLATFORM" ]; then
|
||||
DOCKER_CMD="$DOCKER_CMD --platform $PLATFORM"
|
||||
fi
|
||||
|
||||
# Add no-cache flag if specified
|
||||
if [ "$NO_CACHE" = true ]; then
|
||||
DOCKER_CMD="$DOCKER_CMD --no-cache"
|
||||
fi
|
||||
|
||||
# Add quiet flag if specified
|
||||
if [ "$QUIET" = true ]; then
|
||||
DOCKER_CMD="$DOCKER_CMD --quiet"
|
||||
fi
|
||||
|
||||
# Add build arguments
|
||||
DOCKER_CMD="$DOCKER_CMD $BUILD_ARGS"
|
||||
|
||||
# Add tag and file
|
||||
DOCKER_CMD="$DOCKER_CMD -t $IMAGE_NAME:$TAG"
|
||||
DOCKER_CMD="$DOCKER_CMD -f $DOCKERFILE"
|
||||
DOCKER_CMD="$DOCKER_CMD $CONTEXT"
|
||||
|
||||
log_info "Executing: $DOCKER_CMD"
|
||||
|
||||
# Execute build
|
||||
if eval "$DOCKER_CMD"; then
|
||||
log_success "Docker image built successfully: $IMAGE_NAME:$TAG"
|
||||
else
|
||||
log_error "Docker build failed"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
push_image() {
|
||||
if [ "$PUSH" = true ]; then
|
||||
log_info "Pushing image to registry..."
|
||||
|
||||
if docker push "$IMAGE_NAME:$TAG"; then
|
||||
log_success "Image pushed successfully: $IMAGE_NAME:$TAG"
|
||||
else
|
||||
log_error "Failed to push image"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
show_image_info() {
|
||||
log_info "Image information:"
|
||||
docker images "$IMAGE_NAME:$TAG" --format "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedAt}}\t{{.Size}}"
|
||||
|
||||
# Show image layers (if not quiet)
|
||||
if [ "$QUIET" != true ]; then
|
||||
echo ""
|
||||
log_info "Image layers:"
|
||||
docker history "$IMAGE_NAME:$TAG" --format "table {{.CreatedBy}}\t{{.Size}}" | head -10
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
# Parse command line arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-h|--help)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
-t|--tag)
|
||||
TAG="$2"
|
||||
shift 2
|
||||
;;
|
||||
-e|--env)
|
||||
ENVIRONMENT="$2"
|
||||
shift 2
|
||||
;;
|
||||
-f|--file)
|
||||
DOCKERFILE="$2"
|
||||
shift 2
|
||||
;;
|
||||
-c|--context)
|
||||
CONTEXT="$2"
|
||||
shift 2
|
||||
;;
|
||||
-p|--platform)
|
||||
PLATFORM="$2"
|
||||
shift 2
|
||||
;;
|
||||
--push)
|
||||
PUSH=true
|
||||
shift
|
||||
;;
|
||||
--no-cache)
|
||||
NO_CACHE=true
|
||||
shift
|
||||
;;
|
||||
--quiet)
|
||||
QUIET=true
|
||||
shift
|
||||
;;
|
||||
--build-arg)
|
||||
BUILD_ARGS="$BUILD_ARGS --build-arg $2"
|
||||
shift 2
|
||||
;;
|
||||
--clean)
|
||||
CLEAN=true
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown option: $1"
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Validate environment
|
||||
if [[ "$ENVIRONMENT" != "development" && "$ENVIRONMENT" != "production" ]]; then
|
||||
log_error "Invalid environment: $ENVIRONMENT. Must be 'development' or 'production'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Start build process
|
||||
log_info "Starting TankStopp Docker build process..."
|
||||
|
||||
check_requirements
|
||||
|
||||
if [ "$CLEAN" = true ]; then
|
||||
clean_old_images
|
||||
fi
|
||||
|
||||
prepare_build_args
|
||||
build_image
|
||||
push_image
|
||||
show_image_info
|
||||
|
||||
log_success "Build process completed successfully!"
|
||||
log_info "You can now run the container with:"
|
||||
log_info " docker run -p 8080:8080 $IMAGE_NAME:$TAG"
|
||||
log_info ""
|
||||
log_info "Or use docker-compose:"
|
||||
log_info " docker-compose up -d"
|
||||
}
|
||||
|
||||
# Execute main function
|
||||
main "$@"
|
||||
Executable
+687
@@ -0,0 +1,687 @@
|
||||
#!/bin/bash
|
||||
|
||||
# TankStopp Docker Deployment Script
|
||||
# This script deploys the TankStopp application using Docker Compose
|
||||
|
||||
set -e # Exit on any error
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
PURPLE='\033[0;35m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Default values
|
||||
ENVIRONMENT="production"
|
||||
ACTION="deploy"
|
||||
SERVICE_NAME="tankstopp"
|
||||
COMPOSE_FILE="docker-compose.yml"
|
||||
COMPOSE_OVERRIDE=""
|
||||
PROJECT_NAME="tankstopp"
|
||||
BACKUP_BEFORE_DEPLOY=true
|
||||
WAIT_TIMEOUT=300
|
||||
HEALTH_CHECK_RETRIES=10
|
||||
ROLLBACK_ON_FAILURE=true
|
||||
SCALE_REPLICAS=1
|
||||
|
||||
# Configuration
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
DATA_DIR="/var/lib/tankstopp"
|
||||
BACKUP_DIR="/var/lib/tankstopp/backups"
|
||||
LOG_DIR="/var/log/tankstopp"
|
||||
|
||||
# Functions
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1"
|
||||
}
|
||||
|
||||
log_debug() {
|
||||
if [ "$DEBUG" = true ]; then
|
||||
echo -e "${PURPLE}[DEBUG]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1"
|
||||
fi
|
||||
}
|
||||
|
||||
show_help() {
|
||||
cat << EOF
|
||||
TankStopp Docker Deployment Script
|
||||
|
||||
Usage: $0 [ACTION] [OPTIONS]
|
||||
|
||||
ACTIONS:
|
||||
deploy Deploy the application (default)
|
||||
start Start existing containers
|
||||
stop Stop running containers
|
||||
restart Restart containers
|
||||
update Update to latest images
|
||||
rollback Rollback to previous version
|
||||
status Show deployment status
|
||||
logs Show application logs
|
||||
backup Create database backup
|
||||
restore Restore from backup
|
||||
scale Scale service replicas
|
||||
cleanup Clean up unused resources
|
||||
|
||||
OPTIONS:
|
||||
-h, --help Show this help message
|
||||
-e, --env ENV Environment: development|production|staging (default: production)
|
||||
-f, --file FILE Docker compose file (default: docker-compose.yml)
|
||||
-o, --override FILE Docker compose override file
|
||||
-p, --project NAME Project name (default: tankstopp)
|
||||
-s, --service NAME Service name (default: tankstopp)
|
||||
-r, --replicas COUNT Number of replicas for scaling (default: 1)
|
||||
-t, --timeout SECONDS Wait timeout in seconds (default: 300)
|
||||
--no-backup Skip backup before deployment
|
||||
--no-rollback Don't rollback on failure
|
||||
--force Force action without confirmation
|
||||
--debug Enable debug logging
|
||||
|
||||
EXAMPLES:
|
||||
# Deploy to production
|
||||
$0 deploy --env production
|
||||
|
||||
# Deploy with custom compose file
|
||||
$0 deploy --file docker-compose.prod.yml
|
||||
|
||||
# Start development environment
|
||||
$0 start --env development
|
||||
|
||||
# Scale production service
|
||||
$0 scale --replicas 3
|
||||
|
||||
# View logs
|
||||
$0 logs --service tankstopp
|
||||
|
||||
# Backup database
|
||||
$0 backup
|
||||
|
||||
# Rollback deployment
|
||||
$0 rollback
|
||||
|
||||
ENVIRONMENT CONFIGURATIONS:
|
||||
development - Local development with debug enabled
|
||||
staging - Staging environment for testing
|
||||
production - Production deployment with optimizations
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
check_requirements() {
|
||||
log_info "Checking deployment requirements..."
|
||||
|
||||
# Check Docker
|
||||
if ! command -v docker &> /dev/null; then
|
||||
log_error "Docker is not installed or not in PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check Docker Compose
|
||||
if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then
|
||||
log_error "Docker Compose is not installed or not in PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Set Docker Compose command
|
||||
if docker compose version &> /dev/null; then
|
||||
DOCKER_COMPOSE_CMD="docker compose"
|
||||
else
|
||||
DOCKER_COMPOSE_CMD="docker-compose"
|
||||
fi
|
||||
|
||||
# Check Docker daemon
|
||||
if ! docker info &> /dev/null; then
|
||||
log_error "Docker daemon is not running"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check compose file
|
||||
if [ ! -f "$PROJECT_ROOT/$COMPOSE_FILE" ]; then
|
||||
log_error "Docker Compose file not found: $PROJECT_ROOT/$COMPOSE_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_success "Requirements check passed"
|
||||
}
|
||||
|
||||
setup_environment() {
|
||||
log_info "Setting up environment: $ENVIRONMENT"
|
||||
|
||||
# Create necessary directories
|
||||
create_directories
|
||||
|
||||
# Set compose files based on environment
|
||||
case "$ENVIRONMENT" in
|
||||
"development")
|
||||
COMPOSE_OVERRIDE="docker-compose.override.yml"
|
||||
;;
|
||||
"staging")
|
||||
COMPOSE_OVERRIDE="docker-compose.staging.yml"
|
||||
;;
|
||||
"production")
|
||||
COMPOSE_OVERRIDE="docker-compose.prod.yml"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Build compose command
|
||||
COMPOSE_CMD="$DOCKER_COMPOSE_CMD -p $PROJECT_NAME -f $COMPOSE_FILE"
|
||||
if [ -n "$COMPOSE_OVERRIDE" ] && [ -f "$PROJECT_ROOT/$COMPOSE_OVERRIDE" ]; then
|
||||
COMPOSE_CMD="$COMPOSE_CMD -f $COMPOSE_OVERRIDE"
|
||||
log_info "Using override file: $COMPOSE_OVERRIDE"
|
||||
fi
|
||||
|
||||
log_success "Environment setup completed"
|
||||
}
|
||||
|
||||
create_directories() {
|
||||
log_info "Creating necessary directories..."
|
||||
|
||||
# Create directories with proper permissions
|
||||
sudo mkdir -p "$DATA_DIR/data" "$BACKUP_DIR" "$LOG_DIR"
|
||||
sudo chown -R 1001:1001 "$DATA_DIR" 2>/dev/null || true
|
||||
sudo chmod -R 755 "$DATA_DIR" 2>/dev/null || true
|
||||
|
||||
log_success "Directories created"
|
||||
}
|
||||
|
||||
backup_database() {
|
||||
if [ "$BACKUP_BEFORE_DEPLOY" = false ]; then
|
||||
log_info "Skipping backup (disabled)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_info "Creating database backup..."
|
||||
|
||||
local backup_file="$BACKUP_DIR/fuel_stops_$(date +%Y%m%d_%H%M%S).db"
|
||||
|
||||
# Check if database exists
|
||||
if [ ! -f "$DATA_DIR/data/fuel_stops.db" ]; then
|
||||
log_warning "Database file not found, skipping backup"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Create backup
|
||||
if cp "$DATA_DIR/data/fuel_stops.db" "$backup_file"; then
|
||||
log_success "Database backup created: $backup_file"
|
||||
|
||||
# Keep only last 10 backups
|
||||
find "$BACKUP_DIR" -name "fuel_stops_*.db" -type f | sort -r | tail -n +11 | xargs rm -f 2>/dev/null || true
|
||||
else
|
||||
log_error "Failed to create database backup"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
pull_images() {
|
||||
log_info "Pulling latest images..."
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
if $COMPOSE_CMD pull; then
|
||||
log_success "Images pulled successfully"
|
||||
else
|
||||
log_error "Failed to pull images"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
deploy_application() {
|
||||
log_info "Deploying TankStopp application..."
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
# Backup database
|
||||
backup_database
|
||||
|
||||
# Pull latest images
|
||||
pull_images
|
||||
|
||||
# Deploy with docker-compose
|
||||
log_info "Starting containers..."
|
||||
if $COMPOSE_CMD up -d --remove-orphans; then
|
||||
log_success "Containers started successfully"
|
||||
else
|
||||
log_error "Failed to start containers"
|
||||
if [ "$ROLLBACK_ON_FAILURE" = true ]; then
|
||||
log_info "Attempting rollback..."
|
||||
rollback_deployment
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Wait for services to be healthy
|
||||
wait_for_health
|
||||
|
||||
# Run post-deployment checks
|
||||
post_deployment_checks
|
||||
|
||||
log_success "Application deployed successfully"
|
||||
}
|
||||
|
||||
wait_for_health() {
|
||||
log_info "Waiting for services to become healthy..."
|
||||
|
||||
local retries=0
|
||||
local max_retries=$HEALTH_CHECK_RETRIES
|
||||
|
||||
while [ $retries -lt $max_retries ]; do
|
||||
if check_service_health; then
|
||||
log_success "All services are healthy"
|
||||
return 0
|
||||
fi
|
||||
|
||||
retries=$((retries + 1))
|
||||
log_info "Health check attempt $retries/$max_retries..."
|
||||
sleep 10
|
||||
done
|
||||
|
||||
log_error "Services failed to become healthy within timeout"
|
||||
return 1
|
||||
}
|
||||
|
||||
check_service_health() {
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
# Check container status
|
||||
local unhealthy_containers=$($COMPOSE_CMD ps --filter "status=unhealthy" --format "{{.Service}}" 2>/dev/null | wc -l)
|
||||
local running_containers=$($COMPOSE_CMD ps --filter "status=running" --format "{{.Service}}" 2>/dev/null | wc -l)
|
||||
|
||||
log_debug "Running containers: $running_containers, Unhealthy containers: $unhealthy_containers"
|
||||
|
||||
if [ "$unhealthy_containers" -gt 0 ]; then
|
||||
log_warning "Found $unhealthy_containers unhealthy containers"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "$running_containers" -eq 0 ]; then
|
||||
log_warning "No running containers found"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Test HTTP endpoint
|
||||
if command -v curl &> /dev/null; then
|
||||
local port=$(get_service_port)
|
||||
if curl -sf "http://localhost:$port/" > /dev/null 2>&1; then
|
||||
return 0
|
||||
else
|
||||
log_debug "HTTP health check failed"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
get_service_port() {
|
||||
case "$ENVIRONMENT" in
|
||||
"development")
|
||||
echo "8081"
|
||||
;;
|
||||
*)
|
||||
echo "8080"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
post_deployment_checks() {
|
||||
log_info "Running post-deployment checks..."
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
# Check container logs for errors
|
||||
local error_count=$($COMPOSE_CMD logs --tail=50 "$SERVICE_NAME" 2>/dev/null | grep -i "error\|panic\|fatal" | wc -l)
|
||||
if [ "$error_count" -gt 0 ]; then
|
||||
log_warning "Found $error_count error messages in logs"
|
||||
log_info "Recent errors:"
|
||||
$COMPOSE_CMD logs --tail=10 "$SERVICE_NAME" | grep -i "error\|panic\|fatal" || true
|
||||
fi
|
||||
|
||||
# Check resource usage
|
||||
check_resource_usage
|
||||
|
||||
log_success "Post-deployment checks completed"
|
||||
}
|
||||
|
||||
check_resource_usage() {
|
||||
log_info "Checking resource usage..."
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
# Get container stats
|
||||
local stats=$($COMPOSE_CMD exec -T "$SERVICE_NAME" sh -c "
|
||||
echo 'Memory usage:'
|
||||
free -h | grep Mem
|
||||
echo 'Disk usage:'
|
||||
df -h /app/data
|
||||
echo 'CPU info:'
|
||||
uptime
|
||||
" 2>/dev/null || echo "Could not retrieve stats")
|
||||
|
||||
log_debug "Resource stats: $stats"
|
||||
}
|
||||
|
||||
start_services() {
|
||||
log_info "Starting services..."
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
if $COMPOSE_CMD start; then
|
||||
log_success "Services started successfully"
|
||||
wait_for_health
|
||||
else
|
||||
log_error "Failed to start services"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
stop_services() {
|
||||
log_info "Stopping services..."
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
if $COMPOSE_CMD stop; then
|
||||
log_success "Services stopped successfully"
|
||||
else
|
||||
log_error "Failed to stop services"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
restart_services() {
|
||||
log_info "Restarting services..."
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
if $COMPOSE_CMD restart; then
|
||||
log_success "Services restarted successfully"
|
||||
wait_for_health
|
||||
else
|
||||
log_error "Failed to restart services"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
update_application() {
|
||||
log_info "Updating application..."
|
||||
|
||||
backup_database
|
||||
pull_images
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
if $COMPOSE_CMD up -d --remove-orphans; then
|
||||
log_success "Application updated successfully"
|
||||
wait_for_health
|
||||
post_deployment_checks
|
||||
else
|
||||
log_error "Failed to update application"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
rollback_deployment() {
|
||||
log_info "Rolling back deployment..."
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
# Stop current containers
|
||||
$COMPOSE_CMD down
|
||||
|
||||
# Restore from latest backup
|
||||
local latest_backup=$(find "$BACKUP_DIR" -name "fuel_stops_*.db" -type f | sort -r | head -n 1)
|
||||
if [ -n "$latest_backup" ] && [ -f "$latest_backup" ]; then
|
||||
log_info "Restoring database from: $latest_backup"
|
||||
cp "$latest_backup" "$DATA_DIR/data/fuel_stops.db"
|
||||
log_success "Database restored"
|
||||
else
|
||||
log_warning "No backup found for rollback"
|
||||
fi
|
||||
|
||||
# Start with previous configuration
|
||||
if $COMPOSE_CMD up -d; then
|
||||
log_success "Rollback completed successfully"
|
||||
else
|
||||
log_error "Rollback failed"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
show_status() {
|
||||
log_info "Deployment status:"
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
echo ""
|
||||
echo "=== Container Status ==="
|
||||
$COMPOSE_CMD ps
|
||||
|
||||
echo ""
|
||||
echo "=== Service Health ==="
|
||||
if check_service_health; then
|
||||
echo -e "${GREEN}✓ Services are healthy${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ Services are unhealthy${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Resource Usage ==="
|
||||
check_resource_usage
|
||||
|
||||
echo ""
|
||||
echo "=== Recent Logs ==="
|
||||
$COMPOSE_CMD logs --tail=5 "$SERVICE_NAME"
|
||||
}
|
||||
|
||||
show_logs() {
|
||||
log_info "Showing application logs..."
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
$COMPOSE_CMD logs -f "$SERVICE_NAME"
|
||||
}
|
||||
|
||||
scale_service() {
|
||||
log_info "Scaling service to $SCALE_REPLICAS replicas..."
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
if $COMPOSE_CMD up -d --scale "$SERVICE_NAME=$SCALE_REPLICAS"; then
|
||||
log_success "Service scaled to $SCALE_REPLICAS replicas"
|
||||
wait_for_health
|
||||
else
|
||||
log_error "Failed to scale service"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
cleanup_resources() {
|
||||
log_info "Cleaning up unused resources..."
|
||||
|
||||
# Remove unused containers
|
||||
docker container prune -f
|
||||
|
||||
# Remove unused images
|
||||
docker image prune -f
|
||||
|
||||
# Remove unused volumes (be careful!)
|
||||
if [ "$FORCE" = true ]; then
|
||||
docker volume prune -f
|
||||
fi
|
||||
|
||||
# Remove unused networks
|
||||
docker network prune -f
|
||||
|
||||
log_success "Cleanup completed"
|
||||
}
|
||||
|
||||
restore_backup() {
|
||||
log_info "Available backups:"
|
||||
ls -la "$BACKUP_DIR"/fuel_stops_*.db 2>/dev/null || {
|
||||
log_error "No backups found in $BACKUP_DIR"
|
||||
return 1
|
||||
}
|
||||
|
||||
echo ""
|
||||
read -p "Enter backup filename to restore (or 'latest' for most recent): " backup_choice
|
||||
|
||||
local backup_file
|
||||
if [ "$backup_choice" = "latest" ]; then
|
||||
backup_file=$(find "$BACKUP_DIR" -name "fuel_stops_*.db" -type f | sort -r | head -n 1)
|
||||
else
|
||||
backup_file="$BACKUP_DIR/$backup_choice"
|
||||
fi
|
||||
|
||||
if [ ! -f "$backup_file" ]; then
|
||||
log_error "Backup file not found: $backup_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "Restoring from: $backup_file"
|
||||
|
||||
# Stop services
|
||||
stop_services
|
||||
|
||||
# Restore database
|
||||
cp "$backup_file" "$DATA_DIR/data/fuel_stops.db"
|
||||
|
||||
# Start services
|
||||
start_services
|
||||
|
||||
log_success "Database restored successfully"
|
||||
}
|
||||
|
||||
main() {
|
||||
# Parse command line arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
deploy|start|stop|restart|update|rollback|status|logs|backup|restore|scale|cleanup)
|
||||
ACTION="$1"
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
-e|--env)
|
||||
ENVIRONMENT="$2"
|
||||
shift 2
|
||||
;;
|
||||
-f|--file)
|
||||
COMPOSE_FILE="$2"
|
||||
shift 2
|
||||
;;
|
||||
-o|--override)
|
||||
COMPOSE_OVERRIDE="$2"
|
||||
shift 2
|
||||
;;
|
||||
-p|--project)
|
||||
PROJECT_NAME="$2"
|
||||
shift 2
|
||||
;;
|
||||
-s|--service)
|
||||
SERVICE_NAME="$2"
|
||||
shift 2
|
||||
;;
|
||||
-r|--replicas)
|
||||
SCALE_REPLICAS="$2"
|
||||
shift 2
|
||||
;;
|
||||
-t|--timeout)
|
||||
WAIT_TIMEOUT="$2"
|
||||
shift 2
|
||||
;;
|
||||
--no-backup)
|
||||
BACKUP_BEFORE_DEPLOY=false
|
||||
shift
|
||||
;;
|
||||
--no-rollback)
|
||||
ROLLBACK_ON_FAILURE=false
|
||||
shift
|
||||
;;
|
||||
--force)
|
||||
FORCE=true
|
||||
shift
|
||||
;;
|
||||
--debug)
|
||||
DEBUG=true
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown option: $1"
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Validate environment
|
||||
if [[ "$ENVIRONMENT" != "development" && "$ENVIRONMENT" != "staging" && "$ENVIRONMENT" != "production" ]]; then
|
||||
log_error "Invalid environment: $ENVIRONMENT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Start deployment process
|
||||
log_info "Starting TankStopp deployment process..."
|
||||
log_info "Action: $ACTION"
|
||||
log_info "Environment: $ENVIRONMENT"
|
||||
log_info "Project: $PROJECT_NAME"
|
||||
|
||||
check_requirements
|
||||
setup_environment
|
||||
|
||||
# Execute action
|
||||
case "$ACTION" in
|
||||
"deploy")
|
||||
deploy_application
|
||||
;;
|
||||
"start")
|
||||
start_services
|
||||
;;
|
||||
"stop")
|
||||
stop_services
|
||||
;;
|
||||
"restart")
|
||||
restart_services
|
||||
;;
|
||||
"update")
|
||||
update_application
|
||||
;;
|
||||
"rollback")
|
||||
rollback_deployment
|
||||
;;
|
||||
"status")
|
||||
show_status
|
||||
;;
|
||||
"logs")
|
||||
show_logs
|
||||
;;
|
||||
"backup")
|
||||
backup_database
|
||||
;;
|
||||
"restore")
|
||||
restore_backup
|
||||
;;
|
||||
"scale")
|
||||
scale_service
|
||||
;;
|
||||
"cleanup")
|
||||
cleanup_resources
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown action: $ACTION"
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
log_success "Deployment process completed successfully!"
|
||||
}
|
||||
|
||||
# Execute main function
|
||||
main "$@"
|
||||
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