|
|
#!/bin/bash
## Bash wrap script to launch the ``compose`` docker with right options. ## ##
## Launcher ## - should need minimum requirement to run ## - no shell libs ##
[[ "${BASH_SOURCE[0]}" != "${0}" ]] && SOURCED=true
ANSI_ESC=$'\e['
ansi_color() { local choice="$1"
if [ "$choice" == "tty" ]; then if [ -t 1 ]; then choice="yes" else choice="no" fi fi
if [ "$choice" != "no" ]; then
SET_COL_CHAR="${ANSI_ESC}${COL_CHAR}G" SET_COL_STATUS="${ANSI_ESC}${COL_STATUS}G" SET_COL_INFO="${ANSI_ESC}${COL_INFO}G" SET_COL_ELT="${ANSI_ESC}${COL_ELT}G"
SET_BEGINCOL="${ANSI_ESC}0G"
UP="${ANSI_ESC}1A" DOWN="${ANSI_ESC}1B" LEFT="${ANSI_ESC}1D" RIGHT="${ANSI_ESC}1C"
SAVE="${ANSI_ESC}7" RESTORE="${ANSI_ESC}8"
NORMAL="${ANSI_ESC}0m"
GRAY="${ANSI_ESC}1;30m" RED="${ANSI_ESC}1;31m" GREEN="${ANSI_ESC}1;32m" YELLOW="${ANSI_ESC}1;33m" BLUE="${ANSI_ESC}1;34m" PINK="${ANSI_ESC}1;35m" CYAN="${ANSI_ESC}1;36m" WHITE="${ANSI_ESC}1;37m"
DARKGRAY="${ANSI_ESC}0;30m" DARKRED="${ANSI_ESC}0;31m" DARKGREEN="${ANSI_ESC}0;32m" DARKYELLOW="${ANSI_ESC}0;33m" DARKBLUE="${ANSI_ESC}0;34m" DARKPINK="${ANSI_ESC}0;35m" DARKCYAN="${ANSI_ESC}0;36m" DARKWHITE="${ANSI_ESC}0;37m"
SUCCESS=$GREEN WARNING=$YELLOW FAILURE=$RED NOOP=$BLUE ON=$SUCCESS OFF=$FAILURE ERROR=$FAILURE
else
SET_COL_CHAR= SET_COL_STATUS= SET_COL_INFO= SET_COL_ELT=
SET_BEGINCOL=
NORMAL= RED= GREEN= YELLOW= BLUE= GRAY= WHITE=
DARKGRAY= DARKRED= DARKGREEN= DARKYELLOW= DARKBLUE= DARKPINK= DARKCYAN=
SUCCESS= WARNING= FAILURE= NOOP= ON= OFF= ERROR=
fi
ansi_color="$choice"
export SET_COL_CHAR SET_COL_STATUS SET_COL_INFO SET_COL_ELT \ SET_BEGINCOL UP DOWN LEFT RIGHT SAVE RESTORE NORMAL \ GRAY RED GREEN YELLOW BLUE PINK CYAN WHITE DARKGRAY \ DARKRED DARKGREEN DARKYELLOW DARKBLUE DARKPINK DARKCYAN \ SUCCESS WARNING FAILURE NOOP ON OFF ERROR ansi_color }
e() { printf "%s" "$*"; }
warn() { e "${YELLOW}Warning:$NORMAL" "$*"$'\n' >&2 ; } info() { e "${BLUE}II$NORMAL" "$*"$'\n' >&2 ; } verb() { [ -z "$VERBOSE" ] || e "$*"$'\n' >&2; } debug() { [ -z "$DEBUG" ] || e "$*"$'\n' >&2; } err() { e "${RED}Error:$NORMAL $*"$'\n' >&2 ; } die() { err "$@" ; exit 1; }
ansi_color "${ansi_color:-tty}"
get_path() { ( IFS=: for d in $PATH; do filename="$d/$1" [ -f "$filename" -a -x "$filename" ] && { echo "$d/$1" return 0 } done return 1 ) }
depends() { ## Avoid colliding with variables that are created with depends. local __i __path __new_name
for __i in "$@"; do if ! __path=$(get_path "$__i"); then __new_name="$(echo "${__i//-/_}")" if [ "$__new_name" != "$__i" ]; then depends "$__new_name" else err "dependency check: couldn't find '$__i' required command." exit 1 fi else if ! test -z "$__path" ; then export "$(echo "${__i//- /__}")"="$__path" fi fi done }
get_os() { local uname_output uname_output="$(uname -s)" case "${uname_output}" in Linux*) e linux;; Darwin*) e mac;; CYGWIN*) e cygwin;; MINGW*) e mingw;; *) e "UNKNOWN:${uname_output}";; esac }
read-0() { local eof= IFS='' while [ "$1" ]; do read -r -d '' -- "$1" || eof=1 shift done [ -z "$eof" ] }
read-0a() { local eof= IFS='' while [ "$1" ]; do IFS='' read -r -d $'\n' -- "$1" || eof=1 shift done [ -z "$eof" ] }
p0() { printf "%s\0" "$@" }
list_compose_vars() { while read-0a def; do def="${def##* }" def="${def%=*}" p0 "$def" done < <(declare -p | grep "^declare -x COMPOSE_[A-Z_]\+=\"") }
get_running_compose_containers() { ## XXXvlab: docker bug: there will be a final newline anyway docker ps --filter label="compose.service" --format='{{.ID}}' }
get_volumes_for_container() { local container="$1"
docker inspect \ --format '{{range $mount := .Mounts}}{{$mount.Source}}{{"\x00"}}{{$mount.Destination}}{{"\x00"}}{{end}}' \ "$container" }
is_volume_used() { local volume="$1"
while read container_id; do while read-0 src dst; do [ "$src" == "$volume" ] && return 0 done < <(get_volumes_for_container "$container_id") done < <(get_running_compose_containers) return 1 }
clean_unused_sessions() { for f in "$SESSION_DIR/"*; do [ -e "$f" ] || continue is_volume_used "$f" && continue rm -f "$f" done }
relink_subdirs() { local dir for dir in "$@"; do [ -L "$dir" ] || continue target=$(realpath "$dir") [ -d "$target" ] || continue docker_run_opts+=("-v" "$target:$dir") [ -e "$dir/metadata.yml" ] && continue relink_subdirs "$dir"/* done }
mk_docker_run_options() {
## Order matters, files get to override vars compose_config_files=( ## DEFAULT LINUX VARS /etc/default/charm /etc/default/datastore /etc/default/compose
## COMPOSE SYSTEM-WIDE FILES /etc/compose.conf /etc/compose.local.conf /etc/compose/local.conf
## COMPOSE USER FILES ~/.compose/etc/local.conf ~/.compose.conf )
docker_run_opts=("-v" "/var/run/docker.sock:/var/run/docker.sock")
## current dir if parent=$(while true; do [ -e "./compose.yml" ] && { echo "$PWD" exit 0 } [ "$PWD" == "/" ] && exit 1 cd .. done ); then docker_path=$COMPOSE_VAR/root/$(basename "$parent") docker_run_opts+=("-v" "$parent:$docker_path:ro" \ "-w" "$docker_path") fi
## ## Load config files ##
if [ -z "$DISABLE_SYSTEM_CONFIG_FILE" ]; then ## XXXvlab: should provide YML config opportunities in possible parent dirs ? ## userdir ? and global /etc/compose.yml ? for cfgfile in "${compose_config_files[@]}"; do [ -e "$cfgfile" ] || continue docker_run_opts+=("-v" "$cfgfile:$cfgfile:ro") . "$cfgfile" done else docker_run_opts+=("-e" "DISABLE_SYSTEM_CONFIG_FILE=$DISABLE_SYSTEM_CONFIG_FILE") fi
COMPOSE_LOCAL_ROOT=${COMPOSE_LOCAL_ROOT:-"$HOME/.compose"}
case "$(get_os)" in linux) COMPOSE_VAR=${COMPOSE_VAR:-/var/lib/compose} COMPOSE_CACHE=${COMPOSE_CACHE:-/var/cache/compose} DATASTORE=${DATASTORE:-/srv/datastore/data} CONFIGSTORE=${CONFIGSTORE:-/srv/datastore/config} if [ "$UID" == 0 ]; then SESSION_DIR=${SESSION_DIR:-"$COMPOSE_VAR"/sessions} CHARM_STORE=${CHARM_STORE:-/srv/charm-store} TZ_PATH=${TZ_PATH:-"$COMPOSE_VAR"/timezones} else SESSION_DIR=${SESSION_DIR:-"$COMPOSE_LOCAL_ROOT"/sessions} CHARM_STORE=${CHARM_STORE:-"$HOME"/.charm-store} TZ_PATH=${TZ_PATH:-"$COMPOSE_LOCAL_ROOT"/timezones} fi ;; mac) COMPOSE_VAR=${COMPOSE_VAR:-"$COMPOSE_LOCAL_ROOT"/lib} COMPOSE_CACHE=${COMPOSE_CACHE:-"$COMPOSE_LOCAL_ROOT"/cache} SESSION_DIR=${SESSION_DIR:-"$COMPOSE_LOCAL_ROOT"/sessions} DATASTORE=${DATASTORE:-"$COMPOSE_LOCAL_ROOT"/data} CONFIGSTORE=${CONFIGSTORE:-"$COMPOSE_LOCAL_ROOT"/config} CHARM_STORE=${CHARM_STORE:-"$HOME"/.charm-store} TZ_PATH=${TZ_PATH:-"$COMPOSE_LOCAL_ROOT"/timezones} ;; *) echo "System '$os' not supported yet." >&2 exit 1 ;; esac
## get TZ value and prepare TZ_PATH TZ=$(get_tz) || exit 1 mkdir -p "${TZ_PATH}" TZ_PATH="${TZ_PATH}/$(e "$TZ" | sha256sum | cut -c 1-8)" || exit 1 [ -e "$TZ_PATH" ] || e "$TZ" > "$TZ_PATH"
## CACHE/DATA DIRS docker_run_opts+=("-v" "$COMPOSE_VAR:/var/lib/compose") docker_run_opts+=("-v" "$COMPOSE_CACHE:/var/cache/compose")
docker_run_opts+=("-v" "$TZ_PATH:/etc/timezone:ro")
## ## Checking vars ##
## CHARM_STORE [ -e "$CHARM_STORE" ] || mkdir -p "$CHARM_STORE" || exit 1 [ -L "$CHARM_STORE" ] && { CHARM_STORE=$(readlink -f "$CHARM_STORE") || exit 1 }
docker_run_opts+=( "-v" "$CHARM_STORE:/srv/charm-store:ro" "-e" "CHARM_STORE=/srv/charm-store" "-e" "HOST_CHARM_STORE=$CHARM_STORE" ) relink_subdirs "$CHARM_STORE"/*
## DEFAULT_COMPOSE_FILE if [ "${DEFAULT_COMPOSE_FILE+x}" ]; then DEFAULT_COMPOSE_FILE=$(realpath "$DEFAULT_COMPOSE_FILE") dirname=$(dirname "$DEFAULT_COMPOSE_FILE")/ if [ -e "${DEFAULT_COMPOSE_FILE}" ]; then docker_run_opts+=("-v" "$dirname:$dirname:ro") fi fi
## COMPOSE_YML_FILE if [ "${COMPOSE_YML_FILE+x}" ]; then if [ -e "${COMPOSE_YML_FILE}" ]; then docker_run_opts+=( "-v" "$COMPOSE_YML_FILE:/tmp/compose.yml:ro" "-e" "COMPOSE_YML_FILE=/tmp/compose.yml" "-e" "HOST_COMPOSE_YML_FILE=/tmp/compose.yml" ) fi fi
## DATASTORE and CONFIGSTORE docker_run_opts+=(
"-v" "$DATASTORE:/srv/datastore/data:rw" "-e" "DATASTORE=/srv/datastore/data" "-e" "HOST_DATASTORE=$DATASTORE"
"-v" "$CONFIGSTORE:/srv/datastore/config:rw" "-e" "CONFIGSTORE=/srv/datastore/config" "-e" "HOST_CONFIGSTORE=$CONFIGSTORE" )
docker_run_opts+=("-v" "$HOME/.docker:/root/.docker")
COMPOSE_DOCKER_IMAGE=${COMPOSE_DOCKER_IMAGE:-docker.0k.io/compose} docker_run_opts+=("-e" "COMPOSE_DOCKER_IMAGE=$COMPOSE_DOCKER_IMAGE")
## SSH config docker_run_opts+=( "-v" "$HOME/.ssh:/root/.ssh:ro" "-v" "/etc/ssh:/etc/ssh:ro" )
COMPOSE_LAUNCHER_BIN=$(readlink -f "${BASH_SOURCE[0]}") docker_run_opts+=("-v" "$COMPOSE_LAUNCHER_BIN:/usr/local/bin/compose")
while read-0 var; do case "$var" in COMPOSE_YML_FILE|COMPOSE_LAUNCHER_BIN|COMPOSE_DOCKER_IMAGE|\ COMPOSE_LAUNCHER_OPTS|COMPOSE_VAR|COMPOSE_CACHE) : ;; *) docker_run_opts+=("-e" "$var=${!var}") ;; esac done < <(list_compose_vars)
filename=$(mktemp -p /tmp/ -t launch_opts-XXXXXXXXXXXXXXXX) { p0 "${docker_run_opts[@]}" } > "$filename" sha=$(sha256sum "$filename") sha=${sha:0:64} src="$SESSION_DIR/$UID-$sha" dest="/var/lib/compose/sessions/$UID-$sha" { p0 "-v" "$SESSION_DIR/$UID-$sha:$dest:ro" p0 "-e" "COMPOSE_LAUNCHER_OPTS=$dest" p0 "-e" "COMPOSE_LAUNCHER_BIN=$COMPOSE_LAUNCHER_BIN" } >> "$filename"
mkdir -p "$SESSION_DIR" || return 1 mv -f "$filename" "$SESSION_DIR/$UID-$sha" || return 1 echo "$SESSION_DIR/$UID-$sha"
if [ -n "$DEBUG" ]; then echo "${WHITE}Environment:${NORMAL}" echo " COMPOSE_DOCKER_IMAGE: $COMPOSE_DOCKER_IMAGE" echo " CHARM_STORE: $CHARM_STORE" echo " DATASTORE: $DATASTORE" echo " CONFIGSTORE: $CONFIGSTORE" echo " COMPOSE_VAR: $COMPOSE_VAR" echo " COMPOSE_CACHE: $COMPOSE_CACHE" echo " SESSION_DIR: $SESSION_DIR" echo " TZ_PATH: $TZ_PATH" fi >&2 }
get_tz() { if [ -n "$TZ" ]; then : elif [ -n "$COMPOSE_LOCAL_ROOT" ] && ## previous compose run [ -e "$COMPOSE_LOCAL_ROOT/etc/timezone" ]; then read -r TZ < "$COMPOSE_LOCAL_ROOT/etc/timezone" elif [ -e "/etc/timezone" ]; then ## debian host system timezone read -r TZ < /etc/timezone elif [ -e "/etc/localtime" ]; then ## redhat and macosx sys timezone local fullpath dirname fullpath="$(readlink -f /etc/localtime)" dirname="${fullpath%/*}" TZ=${TZ:-${fullpath##*/}/${dirname##*/}} else err "Timezone not found nor inferable !" echo " compose relies on '/etc/timezone' or '/etc/localtime' to be present " >&2 echo " so as to ensure same timezone for all containers that need it." >&2 echo >&2 echo " You can set a default value for compose by create issuing:" >&2 echo >&2 if [ -n "$COMPOSE_LOCAL_ROOT" ] && [ "$UID" != 0 ]; then echo " mkdir -p $COMPOSE_LOCAL_ROOT/etc &&" >&2 echo " echo \"Europe/Paris\" > $COMPOSE_LOCAL_ROOT/etc/timezone" >&2 else echo " echo \"Europe/Paris\" > /etc/timezone" >&2 fi echo >&2 echo " Of course, you can change 'Europe/Paris' value by any other valid timezone." >&2 echo " timezone." >&2 echo >&2 echo " Notice you can also use \$TZ environment variable, but the value" >&2 echo " will be necessary each time you launch compose." >&2 echo >&2 return 1 fi e "$TZ" }
pretty_print() { while [ "$#" != 0 ]; do case "$1" in "-v"|"-e"|"-w") e "$1" "$2" "\\"$'\n' shift;; *) e "$1 ";; esac shift done }
run() { local os docker_run_opts
docker_run_opts=() if [ -z "$COMPOSE_LAUNCHER_OPTS" ]; then clean_unused_sessions COMPOSE_LAUNCHER_OPTS="$(mk_docker_run_options)" fi
while read-0 opt; do docker_run_opts+=("$opt") ## catch COMPOSE_DOCKER_IMAGE if [[ "$env" == "true" && "$opt" == "COMPOSE_DOCKER_IMAGE="* ]]; then COMPOSE_DOCKER_IMAGE=${opt##COMPOSE_DOCKER_IMAGE=} elif [ "$opt" == "-e" ]; then env=true else env= fi done < <(cat "$COMPOSE_LAUNCHER_OPTS")
[ -t 0 ] && docker_run_opts+=("-i") [ -t 1 ] && docker_run_opts+=("-t")
if [ -n "$DEBUG" ] || [ -n "$DRY_RUN" ]; then debug "${WHITE}Launching:${NORMAL}" echo "docker run --rm \\" pretty_print "${docker_run_opts[@]}" | sed -r 's/^/ /g;s/([^\])$/\1\\\n/g' echo " ${COMPOSE_DOCKER_IMAGE} \\" echo " " "$@" fi | { if [ -n "$DEBUG" ]; then sed -r 's/^/ /g'; else cat; fi } >&2 if [ -z "$DRY_RUN" ]; then debug "${WHITE}Execution:${NORMAL}" exec docker run --rm "${docker_run_opts[@]}" "${COMPOSE_DOCKER_IMAGE}" "$@" fi }
[ "$SOURCED" ] && return 0
## ## Code ##
depends readlink
run "$@"
|