Initial commit: Dokku Docker Compose plugin with test infrastructure
This commit is contained in:
@@ -122,26 +122,26 @@ save_config() {
|
||||
chmod 600 "$PLUGIN_CONFIG_FILE"
|
||||
}
|
||||
|
||||
# Show help message
|
||||
# Show help
|
||||
show_help() {
|
||||
cat <<EOF
|
||||
Usage: dokku docker-compose:COMMAND [options]
|
||||
Usage: dokku docker-compose:COMMAND [OPTIONS] [ARGS]...
|
||||
|
||||
Manage Docker Compose deployments in Dokku.
|
||||
Manage Docker Compose deployments in Dokku
|
||||
|
||||
Commands:
|
||||
import [path] Import a docker-compose.yml file
|
||||
help Show this help message
|
||||
version Show version information
|
||||
plugin:install Install the plugin
|
||||
plugin:uninstall Uninstall the plugin
|
||||
help, --help, -h Show this help message
|
||||
version, --version Show version information
|
||||
import Import a Docker Compose file
|
||||
plugin:install Install the plugin
|
||||
plugin:uninstall Uninstall the plugin
|
||||
|
||||
Options:
|
||||
-f, --file FILE Specify an alternate compose file (default: docker-compose.yml)
|
||||
-p, --project NAME Specify an alternate project name (default: directory name)
|
||||
--dry-run Show what would be created without making changes
|
||||
-v, --verbose Increase verbosity
|
||||
-q, --quiet Suppress output
|
||||
Import Options:
|
||||
-f, --file FILE Specify an alternate compose file (default: docker-compose.yml)
|
||||
-p, --project NAME Specify an alternate project name (default: directory name)
|
||||
--dry-run Show what would be done without making any changes
|
||||
-v, --verbose Show more detailed output
|
||||
-q, --quiet Suppress output
|
||||
|
||||
Examples:
|
||||
# Import default docker-compose.yml in current directory
|
||||
|
||||
303
functions/parser
Normal file
303
functions/parser
Normal file
@@ -0,0 +1,303 @@
|
||||
#!/usr/bin/env bash
|
||||
# Docker Compose parser functions
|
||||
|
||||
# Check if yq is installed
|
||||
check_yq() {
|
||||
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 Docker Compose file
|
||||
validate_compose_file() {
|
||||
local file="${1:?file must be provided}"
|
||||
|
||||
if [[ ! -f "$file" ]]; then
|
||||
log_fail "Compose file not found: $file"
|
||||
fi
|
||||
|
||||
if ! yq eval 'true' "$file" &> /dev/null; then
|
||||
log_fail "Invalid YAML in compose file: $file"
|
||||
fi
|
||||
|
||||
local version
|
||||
version=$(yq eval '.version // ""' "$file")
|
||||
|
||||
if [[ -z "$version" ]]; then
|
||||
log_warn "No version specified in compose file, assuming version 3"
|
||||
version="3"
|
||||
fi
|
||||
|
||||
# Check if version is supported
|
||||
if [[ ! "$version" =~ ^[23](\.\d+)?$ ]]; then
|
||||
log_fail "Unsupported Compose file version: $version. Only versions 2.x and 3.x are supported."
|
||||
fi
|
||||
|
||||
log_debug "Validated Compose file $file (version $version)"
|
||||
echo "$version"
|
||||
}
|
||||
|
||||
# Get all services from compose file
|
||||
get_services() {
|
||||
local file="${1:?file must be provided}"
|
||||
yq eval '.services | keys | .[]' "$file"
|
||||
}
|
||||
|
||||
# Get service configuration
|
||||
get_service_config() {
|
||||
local file="${1:?file must be provided}"
|
||||
local service="${2:?service must be provided}"
|
||||
|
||||
yq eval ".services.$service" "$file"
|
||||
}
|
||||
|
||||
# Get service image
|
||||
get_service_image() {
|
||||
local file="${1:?file must be provided}"
|
||||
local service="${2:?service must be provided}"
|
||||
|
||||
yq eval ".services.$service.image" "$file"
|
||||
}
|
||||
|
||||
# Get service ports
|
||||
get_service_ports() {
|
||||
local file="${1:?file must be provided}"
|
||||
local service="${2:?service must be provided}"
|
||||
|
||||
yq eval ".services.$service.ports[]?" "$file" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# Get service environment variables
|
||||
get_service_environment() {
|
||||
local file="${1:?file must be provided}"
|
||||
local service="${2:?service must be provided}"
|
||||
|
||||
# Handle both array and map formats for environment
|
||||
if yq eval ".services.$service.environment | type" "$file" 2>/dev/null | grep -q '!!map'; then
|
||||
# Map format (KEY: VALUE)
|
||||
yq eval ".services.$service.environment | to_entries | .[] | \"\(.key)=\(.value | tostring)\"" "$file"
|
||||
else
|
||||
# Array format (KEY=VALUE)
|
||||
yq eval ".services.$service.environment[]?" "$file" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
# Get service volumes
|
||||
get_service_volumes() {
|
||||
local file="${1:?file must be provided}"
|
||||
local service="${2:?service must be provided}"
|
||||
|
||||
yq eval ".services.$service.volumes[]?" "$file" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# Get service networks
|
||||
get_service_networks() {
|
||||
local file="${1:?file must be provided}"
|
||||
local service="${2:?service must be provided}"
|
||||
|
||||
yq eval ".services.$service.networks[]?" "$file" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# Get service depends_on
|
||||
get_service_depends_on() {
|
||||
local file="${1:?file must be provided}"
|
||||
local service="${2:?service must be provided}"
|
||||
|
||||
# Handle both array and map formats for depends_on
|
||||
if yq eval ".services.$service.depends_on | type" "$file" 2>/dev/null | grep -q '!!map'; then
|
||||
# Map format (service: condition)
|
||||
yq eval ".services.$service.depends_on | keys | .[]" "$file" 2>/dev/null || true
|
||||
else
|
||||
# Array format (service names)
|
||||
yq eval ".services.$service.depends_on[]?" "$file" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
# Get all networks from compose file
|
||||
get_networks() {
|
||||
local file="${1:?file must be provided}"
|
||||
yq eval '.networks | keys | .[]' "$file" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# Get network configuration
|
||||
get_network_config() {
|
||||
local file="${1:?file must be provided}"
|
||||
local network="${2:?network must be provided}"
|
||||
|
||||
yq eval ".networks.$network" "$file"
|
||||
}
|
||||
|
||||
# Get all volumes from compose file
|
||||
get_volumes() {
|
||||
local file="${1:?file must be provided}"
|
||||
yq eval '.volumes | keys | .[]' "$file" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# Get volume configuration
|
||||
get_volume_config() {
|
||||
local file="${1:?file must be provided}"
|
||||
local volume="${2:?volume must be provided}"
|
||||
|
||||
yq eval ".volumes.$volume" "$file"
|
||||
}
|
||||
|
||||
# Parse a volume string into its components
|
||||
# Format: [host:]container[:mode]
|
||||
parse_volume() {
|
||||
local volume="${1:?volume must be provided}"
|
||||
|
||||
if [[ "$volume" == *:*:* ]]; then
|
||||
# Format: host:container:mode
|
||||
IFS=':' read -r host_path container_path mode <<< "$volume"
|
||||
elif [[ "$volume" == *:* ]]; then
|
||||
# Format: host:container
|
||||
IFS=':' read -r host_path container_path <<< "$volume"
|
||||
mode="rw"
|
||||
else
|
||||
# Format: container
|
||||
container_path="$volume"
|
||||
host_path=""
|
||||
mode="rw"
|
||||
fi
|
||||
|
||||
# Remove any quotes that might be present
|
||||
host_path=${host_path//\"/}
|
||||
container_path=${container_path//\"/}
|
||||
mode=${mode//\"/}
|
||||
|
||||
echo "$host_path"
|
||||
echo "$container_path"
|
||||
echo "$mode"
|
||||
}
|
||||
|
||||
# Build a dependency graph for services
|
||||
build_dependency_graph() {
|
||||
local file="${1:?file must be provided}"
|
||||
declare -A deps
|
||||
|
||||
while IFS= read -r service; do
|
||||
local service_deps=()
|
||||
while IFS= read -r dep; do
|
||||
service_deps+=("$dep")
|
||||
done < <(get_service_depends_on "$file" "$service")
|
||||
|
||||
if [[ ${#service_deps[@]} -gt 0 ]]; then
|
||||
deps["$service"]="${service_deps[*]}"
|
||||
fi
|
||||
done < <(get_services "$file")
|
||||
|
||||
declare -p deps
|
||||
}
|
||||
|
||||
# Get a topological sort of services based on dependencies
|
||||
get_sorted_services() {
|
||||
local file="${1:?file must be provided}"
|
||||
|
||||
# Use tsort to get a topological sort
|
||||
declare -A visited
|
||||
declare -a sorted
|
||||
|
||||
while IFS= read -r service; do
|
||||
if [[ -z "${visited[$service]}" ]]; then
|
||||
_visit "$service" "$file"
|
||||
fi
|
||||
done < <(get_services "$file")
|
||||
|
||||
# Reverse the order to get dependencies first
|
||||
for ((i=${#sorted[@]}-1; i>=0; i--)); do
|
||||
echo "${sorted[$i]}"
|
||||
done
|
||||
}
|
||||
|
||||
# Helper function for topological sort
|
||||
_visit() {
|
||||
local service="$1"
|
||||
local file="$2"
|
||||
|
||||
if [[ "${visited[$service]}" == "visiting" ]]; then
|
||||
log_fail "Circular dependency detected involving service: $service"
|
||||
fi
|
||||
|
||||
if [[ -z "${visited[$service]}" ]]; then
|
||||
visited["$service"]="visiting"
|
||||
|
||||
# Visit all dependencies
|
||||
while IFS= read -r dep; do
|
||||
_visit "$dep" "$file"
|
||||
done < <(get_service_depends_on "$file" "$service")
|
||||
|
||||
visited["$service"]="visited"
|
||||
sorted+=("$service")
|
||||
fi
|
||||
}
|
||||
|
||||
# Get the Dokku app name for a service
|
||||
get_dokku_app_name() {
|
||||
local service="${1:?service must be provided}"
|
||||
local prefix="${2:-}"
|
||||
local suffix="${3:-}"
|
||||
|
||||
# Convert service name to valid Dokku app name
|
||||
# - Convert to lowercase
|
||||
# - Replace underscores and dots with hyphens
|
||||
# - Remove any invalid characters
|
||||
local app_name=$(echo "$service" | tr '[:upper:]' '[:lower:]' | tr '_.' '-' | tr -cd '[:alnum:]-')
|
||||
|
||||
# Apply prefix and suffix if provided
|
||||
echo "${prefix}${app_name}${suffix}"
|
||||
}
|
||||
|
||||
# Check if a service should be managed by a Dokku plugin
|
||||
should_use_dokku_plugin() {
|
||||
local image="${1:?image must be provided}"
|
||||
|
||||
# List of known database and service images that have Dokku plugins
|
||||
local plugin_images=(
|
||||
"postgres"
|
||||
"mysql"
|
||||
"mariadb"
|
||||
"redis"
|
||||
"memcached"
|
||||
"mongodb"
|
||||
"couchdb"
|
||||
"rabbitmq"
|
||||
"elasticsearch"
|
||||
"kibana"
|
||||
"influxdb"
|
||||
"cassandra"
|
||||
"rethinkdb"
|
||||
"neo4j"
|
||||
)
|
||||
|
||||
for plugin_image in "${plugin_images[@]}"; do
|
||||
if [[ "$image" == *"$plugin_image"* ]]; then
|
||||
echo "$plugin_image"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# Get the Dokku plugin command for a service
|
||||
get_dokku_plugin_command() {
|
||||
local plugin="${1:?plugin must be provided}"
|
||||
|
||||
case "$plugin" in
|
||||
postgres) echo "postgres:create" ;;
|
||||
mysql) echo "mysql:create" ;;
|
||||
mariadb) echo "mariadb:create" ;;
|
||||
redis) echo "redis:create" ;;
|
||||
memcached) echo "memcached:create" ;;
|
||||
mongodb) echo "mongodb:create" ;;
|
||||
couchdb) echo "couchdb:create" ;;
|
||||
rabbitmq) echo "rabbitmq:create" ;;
|
||||
elasticsearch) echo "elasticsearch:create" ;;
|
||||
kibana) echo "kibana:create" ;;
|
||||
influxdb) echo "influxdb:create" ;;
|
||||
cassandra) echo "cassandra:create" ;;
|
||||
rethinkdb) echo "rethinkdb:create" ;;
|
||||
neo4j) echo "neo4j:create" ;;
|
||||
*) echo "" ;;
|
||||
esac
|
||||
}
|
||||
Reference in New Issue
Block a user