1064 lines
30 KiB
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 "$@"
|