#!/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" == "" ]]; 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 }