dx/dxkit/modules/docker.module.sh

305 lines
No EOL
8.9 KiB
Bash

#!/usr/bin/env bash
# shellcheck disable=SC2120
# ===========================================
# Load
# ===========================================
function docker::on_load() { load_module 'docker/*'; }
# ===========================================
# Docker
# ===========================================
# Non-interactive docker calls — bypasses all shell aliases.
function docker::raw() {
command docker "$@"
}
# Interactive docker calls that need a TTY.
# On Windows: prepends winpty to allocate a pseudo-terminal.
# On Unix: same as docker::raw.
function docker::tty() {
if platform::is_windows; then
winpty docker "$@"
else
command docker "$@"
fi
}
function docker::exec() {
local container="$1"
shift
docker::tty exec -it "$container" "$@"
}
function docker::container_running() {
local name="$1"
docker::raw ps --format '{{.Names}}' \
| grep -qx "$name"
}
function docker::container_exists() {
local name="$1"
docker::raw ps -a --format '{{.Names}}' \
| grep -qx "$name"
}
function docker::build() {
local dockerfile=$(docker::image::file)
local tag="${APP_IMAGE}:${ENVIRONMENT}"
require_file "$dockerfile" || {
log::docker_error "Dockerfile not found for environment $ENVIRONMENT (Dockerfile-${ENVIRONMENT})!"
return 1
}
log::docker_build "Building image ${tag}..."
if docker::raw build -f "$dockerfile" -t "$tag" "$(ctx::root)"; then
log::docker_success "Image built: ${tag}"
else
log::docker_error "Failed to build image: ${tag}"
return 1
fi
}
function docker::list_containers() {
docker::raw ps --filter "name=${PROJECT_NAME}" \
--format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"
}
function docker::status() {
local container="$1"
if ! docker::raw inspect "$container" >/dev/null 2>&1; then
echo "missing"
return
fi
docker::raw inspect -f '{{.State.Status}}' "$container"
}
function docker::health() {
local container="$1"
local health
health="$(docker::raw inspect -f '{{.State.Health.Status}}' "$container" 2>/dev/null)"
if [[ -z "$health" || "$health" == "<no value>" ]]; then
echo "unknown"
else
echo "$health"
fi
}
function docker::inside_container() {
[[ -f /.dockerenv ]]
}
# ===========================================
# Compose
# ===========================================
function docker::compose() {
local stackName="docker-compose-${ENVIRONMENT}.yml"
require_file "$(docker::compose::file)" || {
log::docker_error "Docker Compose Stack not found for Environment $ENVIRONMENT ($stackName)!"
return 1
}
if core::has_tty; then
docker::tty compose -p "${PROJECT_NAME}_${ENVIRONMENT}" -f "$(docker::compose::file)" "$@"
else
docker::raw compose -p "${PROJECT_NAME}_${ENVIRONMENT}" -f "$(docker::compose::file)" "$@"
fi
}
function docker::compose::interactive() {
local file
file="$(docker::compose::file)"
require_file "$file" || {
log::docker_error "Missing compose file"
return 1
}
docker::tty compose \
-p "${PROJECT_NAME}_${ENVIRONMENT}" \
-f "$file" \
"$@"
}
function docker::compose::default_service() { echo "app"; }
function docker::compose::service_running() {
local service="${1:-app}"
local id
id="$(docker::compose ps -q "$service" 2>/dev/null)"
id="${id//$'\r'/}" # strip carriage returns using parameter expansion, no pipe
[[ -n "$id" ]]
}
function docker::compose::ensure_running() {
local service="${1:-app}"
if ! docker::compose::service_running "$service"; then
log::docker_start "Stack not running, starting..."
docker::compose up -d
fi
}
function docker::compose::logs() {
docker::compose logs -f
}
function docker::compose::up() {
docker::compose up -d --remove-orphans \
&& { log::docker_start "Stack started for Environment $ENVIRONMENT"; } \
|| { log::docker_error "Failed to start Stack for Environment $ENVIRONMENT"; return 1; }
}
function docker::compose::down() {
docker::compose down \
&& { log::docker_stop "Stack stopped for Environment $ENVIRONMENT"; } \
|| { log::docker_error "Failed to stop Stack for Environment $ENVIRONMENT"; return 1; }
}
function docker::compose::status() {
local service="${1:-$(docker::compose::default_service)}"
local id
id="$(docker::compose::container "$service")" || {
echo "missing"
return
}
docker::raw inspect -f '{{.State.Status}}' "$id"
}
function docker::compose::exec() {
local service="$1"
shift
docker::compose::interactive exec "$service" "$@"
}
function docker::compose::container() {
local service="$1"
docker::compose ps -q "$service"
}
function docker::compose::service_exists() {
local service="${1:-$(docker::compose::default_service)}"
[[ -n "$(docker::compose::container "$service")" ]]
}
function docker::compose::list_containers() {
docker::compose ps \
--format "table {{.Name}}\t{{.Service}}\t{{.Status}}\t{{.Ports}}"
}
function docker::compose::ports() {
local service="${1:-$(docker::compose::default_service)}"
local id
id="$(docker::compose::container "$service")" || return 1
docker::raw port "$id" 2>/dev/null
}
# ===========================================
# Paths
# ===========================================
function docker::path() {
local dir=""
local file=""
if [[ $# -eq 1 ]]; then
file="$1"
else
dir="$1"
file="$2"
fi
if [[ -n "$dir" ]]; then
echo "$(ctx::docker)/$dir/$file"
else
echo "$(ctx::docker)/$file"
fi
}
function docker::path::relative() { path::relative_to "$(ctx::docker)" "$1"; }
function docker::image::file() { platform::docker_path "$(ctx::docker)/Dockerfile-${ENVIRONMENT}"; }
function docker::compose::file() { platform::docker_path "$(ctx::docker)/docker-compose-${ENVIRONMENT}.yml"; }
function docker::image::path() { path::relative_to_root "$(ctx::docker)/Dockerfile-${ENVIRONMENT}"; }
function docker::compose::path() { path::relative_to_root "$(ctx::docker)/docker-compose-${ENVIRONMENT}.yml"; }
function docker::app::path() { docker::path::relative "$(ctx::root)"; }
function docker::apache::logs_path() { docker::path::relative "$(ctx::artifact)/apache/logs"; }
function docker::apache::vhosts_path() { docker::path::relative "$(ctx::artifact)/apache/vhosts"; }
function docker::apache::certs_path() { docker::path::relative "$(ctx::artifact)/apache/certificates"; }
function docker::apache::xdebug_path() { docker::path::relative "$(ctx::artifact)/php/xdebug.ini"; }
# ===========================================
# Render
# ===========================================
function docker::compose::render_volume() {
local host="$1"
local container="$2"
echo "- ${host}:${container}"
}
function docker::compose::db_volume() {
docker::compose::render_volume \
"$(docker::path::relative "$(db::volume_host_path)")" \
"$(db::volume_container_path)"
}
function docker::compose::volume() { docker::compose::render_volume "$(docker::path::relative "$1")" "$2"; }
# ===========================================
# Generation
# ===========================================
function docker::image::generate() {
local tag="${APP_IMAGE}:${ENVIRONMENT}"
log::docker_build "Generating Docker Image ($tag)..."
template::render \
"$(template::path docker Dockerfile)" \
"$(docker::image::path)"
}
function docker::compose::generate() {
log::docker_build "Generating Docker Compose Stack (${ENVIRONMENT})..."
template::render \
"$(template::path docker docker-compose.yml)" \
"$(docker::compose::path)" \
ENVIRONMENT="$ENVIRONMENT" \
APP_IMAGE="$APP_IMAGE" \
CTX_BUILD="$(docker::app::path)" \
CTX_APP="$(docker::app::path)" \
CTX_APACHE_LOGS="$(docker::apache::logs_path)" \
CTX_APACHE_VHOSTS="$(docker::apache::vhosts_path)" \
CTX_APACHE_CERTS="$(docker::apache::certs_path)" \
CTX_XDEBUG="$(docker::apache::xdebug_path)" \
APP_PATH="$(ctx::container::root)" \
IMAGE_PATH="$(docker::image::path)" \
TRAEFIK_LABELS="$(docker::proxy::traefik_labels "$PROJECT_NAME" "$DOMAIN" "$BACKEND_DOMAIN" 80)" \
DB_ENVIRONMENT="$(docker::db::environment)" \
DB_VOLUMES="$(docker::compose::volume "$(docker::db::external_path)" "$(docker::db::internal_path)")" \
DB_STACK_PORT="$(docker::db::external_port):$(docker::db::internal_port)"
}
# ===========================================
# Build
# ===========================================
function docker::image::build() {
local dockerfile
dockerfile="$(path::relative_to_root "$(docker::image::path)")"
local tag="${APP_IMAGE}:${ENVIRONMENT}"
require_file "$dockerfile" || {
log::docker_error "Dockerfile not found for environment $ENVIRONMENT"
return 1
}
log::docker_build "Building image ${tag}..."
local context
context="$(platform::docker_path "$(ctx::root)")"
if docker::raw build -f "$dockerfile" -t "$tag" "$context"; then
log::docker_success "Image built: ${tag}"
else
return 1
fi
}
function docker::compose::build() {
docker::compose build
}