#!/usr/bin/env bash # Exit on error, undefined variables, and pipe failures set -euo pipefail # Script constants readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" readonly CONFIG_FILE="docker-composer.conf" readonly DOCKER_COMPOSE_FILE="docker-compose.yml" # Color definitions readonly RED='\033[0;31m' readonly GREEN='\033[0;32m' readonly YELLOW='\033[1;33m' readonly NC='\033[0m' # No Color # Logging functions log_info() { echo -e "${GREEN}[INFO]${NC} $1" } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1" >&2 } log_error() { echo -e "${RED}[ERROR]${NC} $1" >&2 } # Validation functions validate_dependencies() { local missing_deps=() if ! command -v docker &> /dev/null; then missing_deps+=("docker") fi if ! command -v awk &> /dev/null; then missing_deps+=("awk") fi if [ ${#missing_deps[@]} -ne 0 ]; then log_error "Missing required dependencies: ${missing_deps[*]}" exit 1 fi } validate_config_file() { if [ ! -f "$CONFIG_FILE" ]; then log_error "Configuration file '$CONFIG_FILE' not found!" exit 1 fi } validate_compose_path() { local compose_path="$1" if [ ! -d "$compose_path" ]; then log_error "Compose path '$compose_path' does not exist or is not a directory!" exit 1 fi } # Function to read and parse configuration parse_config() { local working_dir="$PWD" # Read compose_path compose_path=$(awk -F "=" ' /^compose_path/ { gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2) # Trim whitespace gsub(/\/$/, "", $2) # Remove trailing slash if ($2 ~ /^\//) { print $2 } else { print "'"$working_dir"'/"$2 } } ' "$CONFIG_FILE") if [ -z "$compose_path" ]; then log_error "compose_path not found in configuration" exit 1 fi # Read exclude_containers exclude_containers=$(awk -F "=" ' /^exclude_containers/ { gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2) # Trim whitespace gsub(/ /, "", $2) # Remove spaces print $2 } ' "$CONFIG_FILE") # Convert exclude_containers to array IFS=',' read -ra EXCLUDE_ARRAY <<< "$exclude_containers" } # Function to check if a container should be excluded is_excluded() { local container_name="$1" for excluded in "${EXCLUDE_ARRAY[@]}"; do if [ "$container_name" = "$excluded" ]; then return 0 fi done return 1 } # Function to compose a single container compose_container() { local dir="$1" local container="$2" local compose_file="${dir%/}/$DOCKER_COMPOSE_FILE" if [ ! -f "$compose_file" ]; then log_warn "Skipping $container: $DOCKER_COMPOSE_FILE not found" return 1 } log_info "Composing ${container}..." if docker compose -f "$compose_file" up -d; then log_info "Successfully composed ${container}" return 0 else log_error "Failed to compose ${container}" return 1 fi } main() { # Validate environment and configuration validate_dependencies validate_config_file # Parse configuration parse_config validate_compose_path "$compose_path" # Initialize arrays for directories and containers local compose_dirs=() local containers=() local failed_containers=() # Collect valid directories and container names while IFS= read -r dir; do base_name=$(basename "$dir") if [[ -d "$dir" && ! "$base_name" =~ ^\. && ! $(is_excluded "$base_name") ]]; then compose_dirs+=("$dir/") containers+=("$base_name") fi done < <(find "$compose_path" -maxdepth 1 -mindepth 1 -type d) if [ ${#compose_dirs[@]} -eq 0 ]; then log_warn "No valid containers found to compose" exit 0 fi log_info "Found ${#containers[@]} containers to compose" # Compose containers for i in "${!compose_dirs[@]}"; do if ! compose_container "${compose_dirs[i]}" "${containers[i]}"; then failed_containers+=("${containers[i]}") fi done # Report results if [ ${#failed_containers[@]} -eq 0 ]; then log_info "All containers composed successfully" else log_error "Failed to compose the following containers: ${failed_containers[*]}" exit 1 fi } # Execute main function main "$@"