1064 lines
30 KiB

#!/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
##
## From kal-shlib
##
_sed_compat_load() {
if get_path sed >/dev/null; then
if sed --version >/dev/null 2>&1; then ## GNU
sed_compat() { sed -r "$@"; }
sed_compat_i() { sed -r -i "$@"; }
else ## BSD
sed_compat() { sed -E "$@"; }
sed_compat_i() { sed -E -i "" "$@"; }
fi
else
## Look for ``gsed``
if (get_path gsed && gsed --version) >/dev/null 2>&1; then
sed_compat() { gsed -r "$@"; }
sed_compat_i() { gsed -r -i "$@"; }
else
die "$exname: required GNU or BSD sed not found"
fi
fi
}
## BSD / GNU sed compatibility layer
sed_compat() { _sed_compat_load; sed_compat "$@"; }
hash_get() {
if get_path sha256sum >/dev/null; then
hash_get() { local x; x=$(sha256sum) || return 1; echo "${x:0:32}"; }
elif get_path md5sum >/dev/null; then
hash_get() { local x; x=$(md5sum) || return 1; echo "${x:0:32}"; }
elif get_path md5 >/dev/null; then
hash_get() { md5; }
else
err "required GNU md5sum or BSD md5 not found"
return 1
fi
hash_get
}
## output on stdout the next record on stdin separated by a '\0'
next-0() {
local ans IFS=''
read -r -d '' ans &&
printf "%s" "$ans"
}
array_read-0() {
local elt aname
while true; do
for aname in "$@"; do
declare -n cur="$aname"
elt="$(next-0)" || return 0
cur+=("$elt")
done
done
}
str_pattern_matches() {
local str="$1"
shift
for pattern in "$@"; do
eval "[[ \"$str\" == $pattern ]]" && return 0
done
return 1
}
ansi_color() {
local choice="$1"
if [ "$choice" == "tty" ]; then
if [ -t 1 ]; then
choice="yes"
else
choice="no"
fi
fi
ANSI_ESC=$'\e['
if [ "$choice" != "no" ]; then
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"
else
NORMAL=
RED=
GREEN=
YELLOW=
BLUE=
GRAY=
WHITE=
DARKGRAY=
DARKRED=
DARKGREEN=
DARKYELLOW=
DARKBLUE=
DARKPINK=
DARKCYAN=
DARKWHITE=
fi
ansi_color="$choice"
export NORMAL GRAY RED GREEN YELLOW BLUE PINK CYAN WHITE DARKGRAY \
DARKRED DARKGREEN DARKYELLOW DARKBLUE DARKPINK DARKCYAN \
DARKWHITE 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; }
## equivalent of 'xargs echo' with builtins
nspc() {
local content
content=$(printf "%s " $(cat -))
printf "%s" "${content::-1}"
}
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*)
if [[ "$(< /proc/version)" =~ "@(Microsoft|WSL)" ]]; then
e wsl
# elif [[ "$(< /proc/version)" =~ "@(microsoft|WSL)" ]]; then
# e wsl2
else
e linux
fi
;;
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" "$@"
}
cla.normalize() {
local letters arg i
while [ "$#" != 0 ]; do
arg=$1
case "$arg" in
--)
p0 "$@"
return 0
;;
--*=*|-*=*)
shift
set -- "${arg%%=*}" "${arg#*=}" "$@"
continue
;;
--*|-?) :;;
-*)
letters=${arg:1}
shift
i=${#letters}
while ((i--)); do
set -- -${letters:$i:1} "$@"
done
continue
;;
esac
p0 "$arg"
shift
done
}
docker_has_image() {
local image="$1"
images=$(docker images -q "$image" 2>/dev/null) || {
err "docker images call has failed unexpectedly."
return 1
}
[ -n "$images" ]
}
docker_image_id() {
local image="$1"
image_id=$(docker inspect "$image" --format='{{.Id}}') || return 1
echo "$image_id"
}
##
## Compose-core common functions
##
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}}'
docker ps --filter label="compose" --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
}
_MULTIOPTION_REGEX='^((-[a-zA-Z]|--[a-zA-Z0-9-]+)(, )?)+'
_MULTIOPTION_REGEX_LINE_FILTER=$_MULTIOPTION_REGEX'(\s|=)'
##
## compose launcher functions
##
clean_unused_sessions() {
for f in "$SESSION_DIR/"*; do
[ -e "$f" ] || continue
is_volume_used "$f" && continue
## XXXvlab: the second rmdir should not be useful
rm -f "$f" >/dev/null || rmdir "$f" >/dev/null || {
debug "Unexpected session remnants $f"
}
done
}
check_no_links_subdirs() {
local dir
for dir in "$@"; do
[ -d "$dir" ] || continue
if [ -L "$dir" ]; then
err "Unfortunately, this compose launcher do not support yet symlinks in charm-store."
echo " Found symlink in charm-store: $dir" >&2
return 1
fi
[ -e "$dir/metadata.yml" ] && continue
check_no_links_subdirs "$dir"/* || return 1
done
}
get_override() {
local override
override=$(get_volume_opt "$@") || return 1
if [ -n "$override" ]; then
if ! [ -f "$override" ]; then
err "Invalid override of 'compose-core' detected." \
"File '$override' does not exist on host."
return 1
fi
echo "$override"
fi
}
get_hash_image() {
local compose_docker_image="$1" override="$2"
{
docker_image_id "$compose_docker_image" || {
err "Failed to get docker image id of image '$compose_docker_image'."
return 1
}
p0 ""
[ -n "$override" ] && cat "$override"
true
} | hash_get
return "${PIPESTATUS[0]}"
}
get_compose_file_opt() {
local hash_bin="$1" override="$2" \
cache_file="$COMPOSE_LAUNCHER_CACHE/$FUNCNAME.cache.$(p0 "$@" | hash_get)"
if [ -e "$cache_file" ]; then
cat "$cache_file" &&
touch "$cache_file" || return 1
return 0
fi
shift 2
DC_MATCH_MULTI=$(get_compose_multi_opts_list "$hash_bin" "$override") || return 1
DC_MATCH_SINGLE=$(get_compose_single_opts_list "$hash_bin" "$override") || return 1
while read-0 arg; do
case "$arg" in
"-f"|"--file")
read-0 value
e "$value"
return 0
;;
--*|-*)
if str_pattern_matches "$arg" $DC_MATCH_MULTI; then
read-0 value
opts+=("$arg" "$value")
shift
elif str_pattern_matches "$arg" $DC_MATCH_SINGLE; then
opts+=("$arg")
else
debug "Unknown option '$arg'. Didn't manage to pre-parse correctly options."
return 1
fi
;;
*)
return 1
;;
esac
done < <(cla.normalize "$@") | tee "$cache_file"
}
replace_compose_file_opt() {
local hash_bin="$1" override="$2" args arg \
cache_file="$COMPOSE_LAUNCHER_CACHE/$FUNCNAME.cache.$(p0 "$@" | hash_get)"
if [ -e "$cache_file" ]; then
cat "$cache_file" &&
touch "$cache_file" || return 1
return 0
fi
debug "Replacing '-f|--file' argument in command line."
shift 2
DC_MATCH_MULTI=$(get_compose_multi_opts_list "$hash_bin" "$override") || return 1
DC_MATCH_SINGLE=$(get_compose_single_opts_list "$hash_bin" "$override") || return 1
args=()
while read-0 arg; do
case "$arg" in
"-f"|"--file")
read-0 value
args+=("$arg" "${value##*/}")
;;
--*|-*)
if str_pattern_matches "$arg" $DC_MATCH_MULTI; then
read-0 value
args+=("$arg" "$value")
shift
elif str_pattern_matches "$arg" $DC_MATCH_SINGLE; then
args+=("$arg")
else
err "Unknown option '$arg'. Didn't manage to pre-parse correctly options."
return 1
fi
;;
*)
args+=("$arg")
while read-0 arg; do
args+=("$arg")
done
;;
esac
done < <(cla.normalize "$@")
p0 "${args[@]}" | tee "$cache_file"
}
get_compose_opts_list() {
local hash_bin="$1" override="$2" \
cache_file="$COMPOSE_LAUNCHER_CACHE/$FUNCNAME.cache.$1"
if [ -e "$cache_file" ]; then
cat "$cache_file" &&
touch "$cache_file" || return 1
return 0
fi
debug "Pre-Launching docker to retrieve command line argument definitions."
opts_list=()
if [ -n "$override" ]; then
opts_list+=("-v" "$override:/usr/local/bin/compose-core:ro")
fi
compose_opts_help=$(docker run "${opts_list[@]}" "$COMPOSE_DOCKER_IMAGE" --help 2>/dev/null)
echo "$compose_opts_help" |
grep '^Options:' -A 20000 |
tail -n +2 |
{ cat ; echo; } |
grep -E -m 1 "^\S*\$" -B 10000 |
head -n -1 |
grep -E "^\s+-" |
sed_compat 's/\s+((((-[a-zA-Z]|--[a-zA-Z0-9-]+)( [A-Z=]+|=[^ ]+)?)(, )?)+)\s+.*$/\1/g' |
tee "$cache_file" || return 1
}
multi_opts_filter() {
grep -E "$_MULTIOPTION_REGEX_LINE_FILTER" |
sed_compat "s/^($_MULTIOPTION_REGEX)(\s|=).*$/\1/g" |
tr ',' "\n" | nspc
}
single_opts_filter() {
grep -E -v "$_MULTIOPTION_REGEX_LINE_FILTER" |
tr ',' "\n" | nspc
}
get_compose_multi_opts_list() {
local hash_bin="$1" override="$2" \
cache_file="$COMPOSE_LAUNCHER_CACHE/$FUNCNAME.cache.$1" opts_list
if [ -e "$cache_file" ]; then
cat "$cache_file" &&
touch "$cache_file" || return 1
return 0
fi
opts_list=$(get_compose_opts_list "$hash_bin" "$override") || return 1
echo "$opts_list" | multi_opts_filter | tee "$cache_file"
}
get_compose_single_opts_list() {
local hash_bin="$1" override="$2" \
cache_file="$COMPOSE_LAUNCHER_CACHE/$FUNCNAME.cache.$1" opts_list
if [ -e "$cache_file" ]; then
cat "$cache_file" &&
touch "$cache_file" || return 1
return 0
fi
opts_list=$(get_compose_opts_list "$hash_bin" "$override") || return 1
echo "$opts_list" | single_opts_filter | tee "$cache_file"
}
get_volume_opt() {
local cache_file="$COMPOSE_LAUNCHER_CACHE/$FUNCNAME.cache.$(p0 "$@" | hash_get)"
if [ -e "$cache_file" ]; then
cat "$cache_file" &&
touch "$cache_file" || return 1
return 0
fi
while [ "$#" != 0 ]; do
case "$1" in
"-v")
dst="${2#*:}"
dst="${dst%:*}"
if [ "$dst" == "/usr/local/bin/compose-core" ]; then
override="${2%%:*}"
debug "Override of compose-core found: $override"
fi
shift;;
"-e"|"-w")
shift;;
*)
:
;;
esac
shift
done
{ [ -n "$override" ] && echo "$override"; } | tee "$cache_file"
}
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
}
win_env() { "$CMD" /c "<nul set /p=%${1}%" 2>/dev/null; }
wsl_path_env() { wslpath "$(win_env "${1}")"; }
set_os() {
OS="$(get_os)"
case "$OS" in
linux)
## 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
)
COMPOSE_LOCAL_ROOT=${COMPOSE_LOCAL_ROOT:-"$HOME/.compose"}
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}
COMPOSE_LAUNCHER_CACHE=${COMPOSE_LAUNCHER_CACHE:-"$COMPOSE_CACHE"}
else
SESSION_DIR=${SESSION_DIR:-"$COMPOSE_LOCAL_ROOT"/sessions}
CHARM_STORE=${CHARM_STORE:-"$HOME"/.charm-store}
TZ_PATH=${TZ_PATH:-"$COMPOSE_LOCAL_ROOT"/timezones}
COMPOSE_LAUNCHER_CACHE=${COMPOSE_LAUNCHER_CACHE:-"$COMPOSE_LOCAL_ROOT"/cache}
fi
;;
mac)
## Order matters, files get to override vars
compose_config_files=(
## COMPOSE SYSTEM-WIDE FILES
/etc/compose.conf
/etc/compose.local.conf
/etc/compose/local.conf
## COMPOSE USER FILES
~/.compose/etc/local.conf
~/.compose.conf
)
COMPOSE_LOCAL_ROOT=${COMPOSE_LOCAL_ROOT:-"$HOME/.compose"}
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}
COMPOSE_LAUNCHER_CACHE=${COMPOSE_LAUNCHER_CACHE:-"$COMPOSE_LOCAL_ROOT"/cache}
;;
wsl)
type -p cmd.exe >/dev/null || {
die "cmd.exe is not found in \$PATH."
}
CMD=$(type -p cmd.exe >/dev/null) || {
for p in {/mnt,}/c/WINDOWS/SYSTEM32; do
if [ -x "$p"/cmd.exe ]; then
CMD="$p"/cmd.exe
fi
done
if [ -z "$CMD" ]; then
die "cmd.exe is not found in \$PATH." \
"And could not find it in standard directories."
fi
}
WIN_HOME="$(wsl_path_env UserProfile)"
WIN_PROGRAM_FILES="$(wsl_path_env ProgramFiles)"
## Order matters, files get to override vars
compose_config_files=(
## COMPOSE SYSTEM-WIDE FILES
/etc/compose.conf
/etc/compose.local.conf
/etc/compose/local.conf
## COMPOSE USER FILES
~/.compose/etc/local.conf
~/.compose.conf
## COMPOSE USER FILES
{~,"$WIN_HOME"}/.compose/etc/local.conf
{~,"$WIN_HOME"}/.compose.conf
)
APPDATA_BASE="$WIN_HOME/AppData/Local/Compose"
COMPOSE_LAUNCHER_APP_DIR="$WIN_PROGRAM_FILES/Compose"
COMPOSE_LOCAL_ROOT=${COMPOSE_LOCAL_ROOT:-"$APPDATA_BASE/Launcher"}
COMPOSE_VAR=${COMPOSE_VAR:-"$APPDATA_BASE/lib"}
COMPOSE_CACHE=${COMPOSE_CACHE:-"$APPDATA_BASE/cache"}
DATASTORE=${DATASTORE:-"$APPDATA_BASE/data"}
CONFIGSTORE=${CONFIGSTORE:-"$APPDATA_BASE/config"}
mkdir -p "$COMPOSE_VAR" "$COMPOSE_CACHE" "$DATASTORE" "$CONFIGSTORE" || return 1
SESSION_DIR=${SESSION_DIR:-"$COMPOSE_LOCAL_ROOT"/sessions}
CHARM_STORE=${CHARM_STORE:-"$COMPOSE_LAUNCHER_APP_DIR/charm-store"}
TZ_PATH=${TZ_PATH:-"$COMPOSE_LOCAL_ROOT"/timezones}
COMPOSE_LAUNCHER_CACHE=${COMPOSE_LAUNCHER_CACHE:-"$COMPOSE_LOCAL_ROOT"/cache}
;;
*)
echo "System '$os' not supported yet." >&2
return 1
;;
esac
}
mk_docker_run_options() {
set_os || return 1
docker_run_opts=("-v" "/var/run/docker.sock:/var/run/docker.sock")
##
## 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")
debug "Loading config file '$cfgfile'."
. "$cfgfile"
done
else
docker_run_opts+=("-e" "DISABLE_SYSTEM_CONFIG_FILE=$DISABLE_SYSTEM_CONFIG_FILE")
fi
mkdir -p "$COMPOSE_LAUNCHER_CACHE" || return 1
## get TZ value and prepare TZ_PATH
TZ=$(get_tz) || return 1
mkdir -p "${TZ_PATH}"
TZ_PATH="${TZ_PATH}/$(e "$TZ" | hash_get | cut -c 1-8)" || return 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" || return 1
[ -L "$CHARM_STORE" ] && {
CHARM_STORE=$(readlink -f "$CHARM_STORE") || return 1
}
docker_run_opts+=(
"-v" "$CHARM_STORE:/srv/charm-store:ro"
"-e" "CHARM_STORE=/srv/charm-store"
"-e" "HOST_CHARM_STORE=$CHARM_STORE"
)
check_no_links_subdirs "$CHARM_STORE"/* || return 1
## 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"
)
if [ "$OS" == "linux" ]; then
[ -d "$HOME/.docker" ] && \
docker_run_opts+=("-v" "$HOME/.docker:/root/.docker:ro")
[ -d "$HOME/.ssh" ] && \
docker_run_opts+=("-v" "$HOME/.ssh:/root/.ssh:ro")
[ -d "/etc/ssh" ] && \
docker_run_opts+=("-v" "/etc/ssh:/etc/ssh:ro")
fi
COMPOSE_DOCKER_IMAGE=${COMPOSE_DOCKER_IMAGE:-docker.0k.io/compose}
docker_run_opts+=("-e" "COMPOSE_DOCKER_IMAGE=$COMPOSE_DOCKER_IMAGE")
if ! docker_has_image "$COMPOSE_DOCKER_IMAGE"; then
docker pull "$COMPOSE_DOCKER_IMAGE" || return 1
fi
COMPOSE_LAUNCHER_BIN=$(readlink -f "${BASH_SOURCE[0]}")
docker_run_opts+=("-v" "$COMPOSE_LAUNCHER_BIN:/usr/local/bin/compose")
COMPOSE_LAUNCHER_BIN_OVERRIDE=$(get_override "${docker_run_opts[@]}") || return 1
COMPOSE_LAUNCHER_HASH=$(
get_hash_image "$COMPOSE_DOCKER_IMAGE" "$COMPOSE_LAUNCHER_BIN_OVERRIDE") || return 1
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)
ARG_COMPOSE_FILE=$(
get_compose_file_opt "$COMPOSE_LAUNCHER_HASH" "$COMPOSE_LAUNCHER_BIN_OVERRIDE" \
"$@") || return 1
compose_file="${ARG_COMPOSE_FILE:-$COMPOSE_YML_FILE}"
if [ -z "$compose_file" ]; then
## Find a compose.yml in parents
debug "No config file specified on command line arguments"
debug "Looking for 'compose.yml' in self and parents.."
if parent=$(while true; do
[ -e "./compose.yml" ] && {
echo "$PWD"
exit 0
}
[ "$PWD" == "/" ] && return 1
cd ..
done
); then
compose_file="$(realpath "$parent"/"compose.yml")"
debug " .. found '$compose_file'"
else
debug " .. not found."
fi
fi
if [ -z "$compose_file" ] && [ "${DEFAULT_COMPOSE_FILE+x}" ]; then
debug "Using \$DEFAULT_COMPOSE_FILE value '$DEFAULT_COMPOSE_FILE' as compose file."
compose_file="$DEFAULT_COMPOSE_FILE"
fi
if [ -n "$compose_file" ]; then
if ! [ -f "$compose_file" ]; then
die "Specified compose file '$compose_file' not found."
fi
compose_file="$(realpath "$compose_file")"
if [ "$OS" == "wsl" ]; then
## Docker host is not same linux than WSL, so
## access to root files are not the same.
##YYYvlab, check on cp where is the base
dst="$COMPOSE_LAUNCHER_CACHE/compose.$(hash_get < "$compose_file").yml"
cp "$compose_file" "$dst"
## docker host start with /c/... whereas WSL could start with /mnt/c/...
local="$dst"
else
local="$compose_file"
fi
parent_dir="${compose_file%/*}"
docker_path=/var/lib/compose/root/${parent_dir##*/}/${compose_file##*/}
docker_run_opts+=(
"-e" "COMPOSE_YML_FILE=${compose_file##*/}"
"-e" "HOST_COMPOSE_YML_FILE=${local}"
"-v" "${local}:${docker_path}:ro"
"-w" "${docker_path%/*}"
)
else
docker_path=/var/lib/compose/root
docker_run_opts+=(
"-w" "${docker_path}"
)
fi
clean_unused_sessions
filename=$(mktemp -p /tmp/ -t launch_opts-XXXXXXXXXXXXXXXX)
p0 "${docker_run_opts[@]}" > "$filename"
hash=$(hash_get < "$filename") || return 1
src="$SESSION_DIR/$UID-$hash"
dest="/var/lib/compose/sessions/$UID-$hash"
additional_docker_run_opts=(
"-v" "$SESSION_DIR/$UID-$hash:$dest:ro"
"-e" "HOST_COMPOSE_LAUNCHER_OPTS=$SESSION_DIR/$UID-$hash"
"-e" "COMPOSE_LAUNCHER_OPTS=$dest"
"-e" "COMPOSE_LAUNCHER_BIN=$COMPOSE_LAUNCHER_BIN"
"--label" "compose=1"
)
p0 "${additional_docker_run_opts[@]}" >> "$filename"
docker_run_opts+=("${additional_docker_run_opts[@]}")
## keep also some env vars:
for var in ARG_COMPOSE_FILE COMPOSE_DOCKER_IMAGE COMPOSE_LAUNCHER_{BIN_OVERRIDE,HASH}; do
p0 "!env:$var=${!var}"
done >> "$filename"
mkdir -p "$SESSION_DIR" || return 1
mv -f "$filename" "$src" || return 1
}
load_env() {
docker_run_opts=()
if [ -z "$COMPOSE_LAUNCHER_OPTS" ]; then
mk_docker_run_options "$@" || return 1
else
set_os || return 1
while read-0 opt; do
if [[ "$opt" == "!env:"* ]]; then
opt="${opt##!env:}"
var="${opt%%=*}"
value="${opt#*=}"
debug "Loading var: $var=$value"
export "$var"="$value"
else
docker_run_opts+=("$opt")
fi
done < <(cat "$COMPOSE_LAUNCHER_OPTS")
fi
}
show_env() {
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 " COMPOSE_LAUNCHER_CACHE: $COMPOSE_LAUNCHER_CACHE"
echo " SESSION_DIR: $SESSION_DIR"
echo " TZ_PATH: $TZ_PATH"
}
run() {
local os docker_run_opts
load_env "$@" || return 1
[ -n "$DEBUG" ] && show_env >&2
if [ -n "$ARG_COMPOSE_FILE" ]; then
array_read-0 cmd_args < \
<(replace_compose_file_opt "$COMPOSE_LAUNCHER_HASH" \
"$COMPOSE_LAUNCHER_BIN_OVERRIDE" \
"$@")
set -- "${cmd_args[@]}"
fi
## XXXvlab: can't see a place where we wouldn't want to link stdin
## to internal process be it a terminal or not.
docker_run_opts+=("-i")
## If stdin is a not a tty, then adding ``-t`` will fail
[ -t 0 -a -t 1 ] && docker_run_opts+=("-t")
debug "${WHITE}Launching:${NORMAL}"
if [ -n "$DEBUG" ] || [ -n "$DRY_RUN" ]; then
echo "docker run --rm \\"
pretty_print "${docker_run_opts[@]}" | sed_compat 's/^/ /g;s/([^\])$/\1\\\n/g'
if [ -z "$ENTER" ]; then
printf "%s\n" " ${COMPOSE_DOCKER_IMAGE} \\"
printf " "
printf "%s " "$@"
printf "\n"
else
echo " --entrypoint bash \\"
echo " ${COMPOSE_DOCKER_IMAGE}"
fi
fi | { if [ -n "$DEBUG" ]; then sed_compat 's/^/ /g'; else cat; fi } >&2
if [ -z "$DRY_RUN" ]; then
debug "${WHITE}Execution:${NORMAL}"
if [ -z "$ENTER" ]; then
exec docker run --rm "${docker_run_opts[@]}" "${COMPOSE_DOCKER_IMAGE}" "$@"
else
exec docker run --rm "${docker_run_opts[@]}" \
--entrypoint bash \
"${COMPOSE_DOCKER_IMAGE}"
fi
fi
}
[ -n "$SOURCED" ] && return 0
##
## Code
##
depends docker cat readlink sed realpath tee sed grep tail
ansi_color "${ansi_color:-tty}"
if [ "$SHOW_ENV" ]; then
load_env "$@" || return 1
show_env >&2
exit 0
fi
if [ "$SHOW_CONFIG_LOCATIONS" ]; then
set_os || return 1
echo "compose will read these files if existing in the given order:"
for loc in "${compose_config_files[@]}"; do
echo " - $loc"
done
exit 0
fi
run "$@"