305 lines
No EOL
8.9 KiB
Bash
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
|
|
} |