391 lines
11 KiB
Bash
Executable File
391 lines
11 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Commands for the docker-compose plugin
|
|
|
|
set -eo pipefail
|
|
[[ $DOKKU_TRACE ]] && set -x
|
|
|
|
# Set plugin paths
|
|
PLUGIN_NAME="docker-compose"
|
|
PLUGIN_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
PLUGIN_ENABLED_PATH="/var/lib/dokku/plugins/available/$PLUGIN_NAME"
|
|
PLUGIN_DATA_ROOT="/var/lib/dokku/data/$PLUGIN_NAME"
|
|
PLUGIN_CONFIG_FILE="$PLUGIN_DATA_ROOT/config/config"
|
|
PLUGIN_LOG_FILE="$PLUGIN_DATA_ROOT/logs/plugin.log"
|
|
|
|
# Import common functions
|
|
source "$PLUGIN_PATH/functions/core"
|
|
|
|
# Import parser functions
|
|
source "$PLUGIN_PATH/functions/parser"
|
|
|
|
# Default values
|
|
COMPOSE_FILE="docker-compose.yml"
|
|
PROJECT_NAME="${PWD##*/}"
|
|
DRY_RUN=false
|
|
VERBOSE=false
|
|
QUIET=false
|
|
|
|
# Initialize plugin if needed
|
|
initialize_plugin() {
|
|
# Create required directories if they don't exist
|
|
mkdir -p "$PLUGIN_DATA_ROOT/logs"
|
|
mkdir -p "$PLUGIN_DATA_ROOT/config"
|
|
|
|
# Initialize log file if it doesn't exist
|
|
touch "$PLUGIN_LOG_FILE"
|
|
chmod 666 "$PLUGIN_LOG_FILE"
|
|
|
|
# Initialize config file if it doesn't exist
|
|
if [[ ! -f "$PLUGIN_CONFIG_FILE" ]]; then
|
|
touch "$PLUGIN_CONFIG_FILE"
|
|
chmod 600 "$PLUGIN_CONFIG_FILE"
|
|
# Set default configuration
|
|
echo "DOKKU_DOCKER_COMPOSE_LOG_LEVEL=info" > "$PLUGIN_CONFIG_FILE"
|
|
echo "DOKKU_DOCKER_COMPOSE_MAX_RETRIES=3" >> "$PLUGIN_CONFIG_FILE"
|
|
echo "DOKKU_DOCKER_COMPOSE_TIMEOUT=300" >> "$PLUGIN_CONFIG_FILE"
|
|
fi
|
|
|
|
# Load configuration
|
|
source "$PLUGIN_CONFIG_FILE"
|
|
|
|
# Set log level from config
|
|
case "${DOKKU_DOCKER_COMPOSE_LOG_LEVEL}" in
|
|
debug) LOG_LEVEL=0 ;;
|
|
info) LOG_LEVEL=1 ;;
|
|
warn) LOG_LEVEL=2 ;;
|
|
error) LOG_LEVEL=3 ;;
|
|
fatal) LOG_LEVEL=4 ;;
|
|
esac
|
|
}
|
|
|
|
# Plugin installation
|
|
plugin_install() {
|
|
log_info "Installing $PLUGIN_NAME plugin..."
|
|
|
|
# Check for dependencies
|
|
if ! command -v yq &> /dev/null; then
|
|
log_warn "yq is not installed. It's required for parsing YAML files."
|
|
if [[ -f /etc/os-release ]] && grep -q "debian\|ubuntu" /etc/os-release; then
|
|
log_info "You can install it with: sudo apt-get install yq"
|
|
elif [[ -f /etc/redhat-release ]]; then
|
|
log_info "You can install it with: sudo dnf install yq"
|
|
else
|
|
log_info "Please install yq from https://github.com/mikefarah/yq"
|
|
fi
|
|
fi
|
|
|
|
# Create plugin directory structure
|
|
mkdir -p "$PLUGIN_ENABLED_PATH"
|
|
|
|
# Set up logging
|
|
mkdir -p "$PLUGIN_DATA_ROOT/logs"
|
|
touch "$PLUGIN_LOG_FILE"
|
|
chmod 666 "$PLUGIN_LOG_FILE"
|
|
|
|
# Set up configuration
|
|
mkdir -p "$PLUGIN_DATA_ROOT/config"
|
|
touch "$PLUGIN_CONFIG_FILE"
|
|
chmod 600 "$PLUGIN_CONFIG_FILE"
|
|
|
|
# Set proper permissions
|
|
chown -R dokku:dokku "$PLUGIN_DATA_ROOT"
|
|
chmod -R 755 "$PLUGIN_DATA_ROOT"
|
|
|
|
log_success "$PLUGIN_NAME plugin installed successfully"
|
|
}
|
|
|
|
# Parse command line arguments
|
|
parse_args() {
|
|
local args=("$@")
|
|
local i=0
|
|
|
|
while [[ $i -lt ${#args[@]} ]]; do
|
|
case "${args[$i]}" in
|
|
-f|--file)
|
|
((i++))
|
|
COMPOSE_FILE="${args[$i]}"
|
|
;;
|
|
-p|--project)
|
|
((i++))
|
|
PROJECT_NAME="${args[$i]}"
|
|
;;
|
|
--dry-run)
|
|
DRY_RUN=true
|
|
;;
|
|
-v|--verbose)
|
|
VERBOSE=true
|
|
set -x
|
|
;;
|
|
-q|--quiet)
|
|
QUIET=true
|
|
;;
|
|
--)
|
|
# End of arguments
|
|
((i++))
|
|
break
|
|
;;
|
|
-*)
|
|
log_fail "Unknown option: ${args[$i]}"
|
|
show_help
|
|
exit 1
|
|
;;
|
|
*)
|
|
# Handle non-flag arguments
|
|
if [[ -z "$COMMAND" ]]; then
|
|
COMMAND="${args[$i]}"
|
|
else
|
|
log_fail "Unknown argument: ${args[$i]}"
|
|
show_help
|
|
exit 1
|
|
fi
|
|
;;
|
|
esac
|
|
((i++))
|
|
done
|
|
}
|
|
|
|
# Import a Docker Compose file
|
|
import_compose_file() {
|
|
log_info "Importing Docker Compose file: $COMPOSE_FILE"
|
|
|
|
# Check if yq is installed
|
|
if ! command -v yq &> /dev/null; then
|
|
log_fail "yq is required but not installed. Please install yq (https://github.com/mikefarah/yq) and try again."
|
|
fi
|
|
|
|
# Validate compose file
|
|
log_debug "Validating compose file: $COMPOSE_FILE"
|
|
local version
|
|
version=$(validate_compose_file "$COMPOSE_FILE")
|
|
log_info "Using Docker Compose file version: $version"
|
|
|
|
# Get all services
|
|
log_debug "Discovering services..."
|
|
local services=()
|
|
while IFS= read -r service; do
|
|
services+=("$service")
|
|
log_debug "Found service: $service"
|
|
done < <(get_services "$COMPOSE_FILE")
|
|
|
|
if [[ ${#services[@]} -eq 0 ]]; then
|
|
log_fail "No services found in $COMPOSE_FILE"
|
|
fi
|
|
|
|
log_info "Found ${#services[@]} service(s)"
|
|
|
|
# Process services in dependency order
|
|
log_debug "Determining service dependencies..."
|
|
local sorted_services=()
|
|
while IFS= read -r service; do
|
|
sorted_services+=("$service")
|
|
done < <(get_sorted_services "$COMPOSE_FILE")
|
|
|
|
log_info "Processing services in order: ${sorted_services[*]}"
|
|
|
|
# Process each service
|
|
for service in "${sorted_services[@]}"; do
|
|
log_info "Processing service: $service"
|
|
|
|
# Get service configuration
|
|
local image
|
|
image=$(get_service_image "$COMPOSE_FILE" "$service")
|
|
log_debug "Service image: $image"
|
|
|
|
# Check if this should use a Dokku plugin
|
|
local plugin
|
|
if plugin=$(should_use_dokku_plugin "$image"); then
|
|
log_info "Service '$service' can be managed by the '$plugin' plugin"
|
|
|
|
# Get the plugin create command
|
|
local plugin_cmd
|
|
plugin_cmd=$(get_dokku_plugin_command "$plugin")
|
|
|
|
if [[ -n "$plugin_cmd" ]]; then
|
|
local app_name
|
|
app_name=$(get_dokku_app_name "$service" "$PROJECT_NAME-")
|
|
|
|
log_info "Creating $plugin service: $app_name"
|
|
|
|
if [[ "$DRY_RUN" == false ]]; then
|
|
# Create the service using the plugin
|
|
if dokku "$plugin_cmd" "$app_name"; then
|
|
log_success "Created $plugin service: $app_name"
|
|
|
|
# Link the service to the app if this is not the main app
|
|
if [[ "$service" != "web" && "$service" != "app" ]]; then
|
|
log_info "Linking $plugin service to app"
|
|
# This would be implemented based on the specific plugin
|
|
# dokku "$plugin:link" "$app_name" "$PROJECT_NAME"
|
|
fi
|
|
else
|
|
log_error "Failed to create $plugin service: $app_name"
|
|
fi
|
|
else
|
|
log_info "[DRY RUN] Would create $plugin service: $app_name"
|
|
fi
|
|
else
|
|
log_warn "No create command found for plugin: $plugin"
|
|
fi
|
|
else
|
|
# This is a regular app that needs to be deployed
|
|
log_info "Service '$service' will be deployed as a Dokku app"
|
|
|
|
local app_name
|
|
app_name=$(get_dokku_app_name "$service" "$PROJECT_NAME-")
|
|
|
|
log_info "Creating app: $app_name"
|
|
|
|
if [[ "$DRY_RUN" == false ]]; then
|
|
# Create the app
|
|
if dokku apps:exists "$app_name" &> /dev/null; then
|
|
log_info "App already exists: $app_name"
|
|
else
|
|
if dokku apps:create "$app_name"; then
|
|
log_success "Created app: $app_name"
|
|
else
|
|
log_error "Failed to create app: $app_name"
|
|
continue
|
|
fi
|
|
fi
|
|
|
|
# Set environment variables
|
|
local env_vars=()
|
|
while IFS= read -r env_var; do
|
|
if [[ -n "$env_var" ]]; then
|
|
env_vars+=("$env_var")
|
|
fi
|
|
done < <(get_service_environment "$COMPOSE_FILE" "$service")
|
|
|
|
if [[ ${#env_vars[@]} -gt 0 ]]; then
|
|
log_info "Setting ${#env_vars[@]} environment variables"
|
|
for env_var in "${env_vars[@]}"; do
|
|
log_debug "Setting config: $env_var"
|
|
dokku config:set --no-restart "$app_name" "$env_var"
|
|
done
|
|
fi
|
|
|
|
# Set up volumes
|
|
local volumes=()
|
|
while IFS= read -r volume; do
|
|
if [[ -n "$volume" ]]; then
|
|
volumes+=("$volume")
|
|
fi
|
|
done < <(get_service_volumes "$COMPOSE_FILE" "$service")
|
|
|
|
if [[ ${#volumes[@]} -gt 0 ]]; then
|
|
log_info "Configuring ${#volumes[@]} volumes"
|
|
for volume in "${volumes[@]}"; do
|
|
log_debug "Processing volume: $volume"
|
|
# Parse volume components
|
|
local host_path container_path mode
|
|
read -r host_path container_path mode < <(parse_volume "$volume")
|
|
|
|
if [[ -n "$host_path" && "$host_path" != *"/"* ]]; then
|
|
# Named volume
|
|
log_debug "Creating named volume: $host_path"
|
|
dokku storage:ensure-directory "$host_path"
|
|
dokku storage:mount "$app_name" "/var/lib/dokku/data/storage/$host_path:$container_path:$mode"
|
|
elif [[ -n "$host_path" ]]; then
|
|
# Host path
|
|
log_debug "Mounting host path: $host_path"
|
|
dokku storage:mount "$app_name" "$host_path:$container_path:$mode"
|
|
else
|
|
# Anonymous volume
|
|
log_debug "Creating anonymous volume for: $container_path"
|
|
local volume_name="${app_name}-$(echo "$container_path" | tr -cd '[:alnum:]' | tr '[:upper:]' '[:lower:]')"
|
|
dokku storage:ensure-directory "$volume_name"
|
|
dokku storage:mount "$app_name" "/var/lib/dokku/data/storage/$volume_name:$container_path:$mode"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# Set up ports
|
|
local ports=()
|
|
while IFS= read -r port; do
|
|
if [[ -n "$port" ]]; then
|
|
ports+=("$port")
|
|
fi
|
|
done < <(get_service_ports "$COMPOSE_FILE" "$service")
|
|
|
|
if [[ ${#ports[@]} -gt 0 ]]; then
|
|
log_info "Configuring ${#ports[@]} ports"
|
|
for port in "${ports[@]}"; do
|
|
log_debug "Processing port: $port"
|
|
# Parse port mapping (host:container)
|
|
local host_port container_port
|
|
IFS=':' read -r host_port container_port <<< "$port"
|
|
|
|
if [[ -n "$host_port" && -n "$container_port" ]]; then
|
|
# Remove protocol if present
|
|
container_port="${container_port%%/*}"
|
|
|
|
log_debug "Mapping port $host_port to $container_port"
|
|
dokku proxy:ports-set "$app_name" "http:${host_port}:${container_port}"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# Deploy the app
|
|
log_info "Deploying app: $app_name"
|
|
# This would be implemented with the actual deployment logic
|
|
# dokku git:from-image $app_name $image
|
|
else
|
|
log_info "[DRY RUN] Would create app: $app_name"
|
|
log_info "[DRY RUN] Would set ${#env_vars[@]} environment variables"
|
|
log_info "[DRY RUN] Would configure ${#volumes[@]} volumes"
|
|
log_info "[DRY RUN] Would configure ${#ports[@]} ports"
|
|
log_info "[DRY RUN] Would deploy app with image: $image"
|
|
fi
|
|
fi
|
|
done
|
|
|
|
log_success "Successfully imported $COMPOSE_FILE"
|
|
}
|
|
|
|
# Show help if no arguments are provided
|
|
if [[ $# -eq 0 ]]; then
|
|
show_help
|
|
exit 0
|
|
fi
|
|
|
|
# Initialize the plugin
|
|
initialize_plugin
|
|
|
|
# Parse command line arguments
|
|
parse_args "$@"
|
|
|
|
# Execute the command
|
|
case "$COMMAND" in
|
|
help|--help|-h)
|
|
show_help
|
|
;;
|
|
|
|
version|--version|-v)
|
|
show_version
|
|
;;
|
|
|
|
import)
|
|
shift
|
|
import_compose_file
|
|
;;
|
|
|
|
# Plugin management commands
|
|
plugin:install)
|
|
plugin_install
|
|
;;
|
|
|
|
plugin:uninstall)
|
|
log_info "Uninstalling $PLUGIN_NAME plugin..."
|
|
# Add cleanup logic here
|
|
log_success "$PLUGIN_NAME plugin uninstalled successfully"
|
|
;;
|
|
|
|
*)
|
|
log_fail "Unknown command: ${COMMAND:-}"
|
|
show_help
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
exit 0
|