You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1063 lines
30 KiB

9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
  1. #!/bin/bash
  2. ## Bash wrap script to launch the ``compose`` docker with right options.
  3. ##
  4. ##
  5. ## Launcher
  6. ## - should need minimum requirement to run
  7. ## - no shell libs
  8. ##
  9. [[ "${BASH_SOURCE[0]}" != "${0}" ]] && SOURCED=true
  10. ##
  11. ## From kal-shlib
  12. ##
  13. _sed_compat_load() {
  14. if get_path sed >/dev/null; then
  15. if sed --version >/dev/null 2>&1; then ## GNU
  16. sed_compat() { sed -r "$@"; }
  17. sed_compat_i() { sed -r -i "$@"; }
  18. else ## BSD
  19. sed_compat() { sed -E "$@"; }
  20. sed_compat_i() { sed -E -i "" "$@"; }
  21. fi
  22. else
  23. ## Look for ``gsed``
  24. if (get_path gsed && gsed --version) >/dev/null 2>&1; then
  25. sed_compat() { gsed -r "$@"; }
  26. sed_compat_i() { gsed -r -i "$@"; }
  27. else
  28. die "$exname: required GNU or BSD sed not found"
  29. fi
  30. fi
  31. }
  32. ## BSD / GNU sed compatibility layer
  33. sed_compat() { _sed_compat_load; sed_compat "$@"; }
  34. hash_get() {
  35. if get_path sha256sum >/dev/null; then
  36. hash_get() { local x; x=$(sha256sum) || return 1; echo "${x:0:32}"; }
  37. elif get_path md5sum >/dev/null; then
  38. hash_get() { local x; x=$(md5sum) || return 1; echo "${x:0:32}"; }
  39. elif get_path md5 >/dev/null; then
  40. hash_get() { md5; }
  41. else
  42. err "required GNU md5sum or BSD md5 not found"
  43. return 1
  44. fi
  45. hash_get
  46. }
  47. ## output on stdout the next record on stdin separated by a '\0'
  48. next-0() {
  49. local ans IFS=''
  50. read -r -d '' ans &&
  51. printf "%s" "$ans"
  52. }
  53. array_read-0() {
  54. local elt aname
  55. while true; do
  56. for aname in "$@"; do
  57. declare -n cur="$aname"
  58. elt="$(next-0)" || return 0
  59. cur+=("$elt")
  60. done
  61. done
  62. }
  63. str_pattern_matches() {
  64. local str="$1"
  65. shift
  66. for pattern in "$@"; do
  67. eval "[[ \"$str\" == $pattern ]]" && return 0
  68. done
  69. return 1
  70. }
  71. ansi_color() {
  72. local choice="$1"
  73. if [ "$choice" == "tty" ]; then
  74. if [ -t 1 ]; then
  75. choice="yes"
  76. else
  77. choice="no"
  78. fi
  79. fi
  80. ANSI_ESC=$'\e['
  81. if [ "$choice" != "no" ]; then
  82. NORMAL="${ANSI_ESC}0m"
  83. GRAY="${ANSI_ESC}1;30m"
  84. RED="${ANSI_ESC}1;31m"
  85. GREEN="${ANSI_ESC}1;32m"
  86. YELLOW="${ANSI_ESC}1;33m"
  87. BLUE="${ANSI_ESC}1;34m"
  88. PINK="${ANSI_ESC}1;35m"
  89. CYAN="${ANSI_ESC}1;36m"
  90. WHITE="${ANSI_ESC}1;37m"
  91. DARKGRAY="${ANSI_ESC}0;30m"
  92. DARKRED="${ANSI_ESC}0;31m"
  93. DARKGREEN="${ANSI_ESC}0;32m"
  94. DARKYELLOW="${ANSI_ESC}0;33m"
  95. DARKBLUE="${ANSI_ESC}0;34m"
  96. DARKPINK="${ANSI_ESC}0;35m"
  97. DARKCYAN="${ANSI_ESC}0;36m"
  98. DARKWHITE="${ANSI_ESC}0;37m"
  99. else
  100. NORMAL=
  101. RED=
  102. GREEN=
  103. YELLOW=
  104. BLUE=
  105. GRAY=
  106. WHITE=
  107. DARKGRAY=
  108. DARKRED=
  109. DARKGREEN=
  110. DARKYELLOW=
  111. DARKBLUE=
  112. DARKPINK=
  113. DARKCYAN=
  114. DARKWHITE=
  115. fi
  116. ansi_color="$choice"
  117. export NORMAL GRAY RED GREEN YELLOW BLUE PINK CYAN WHITE DARKGRAY \
  118. DARKRED DARKGREEN DARKYELLOW DARKBLUE DARKPINK DARKCYAN \
  119. DARKWHITE SUCCESS WARNING FAILURE NOOP ON OFF ERROR ansi_color
  120. }
  121. e() { printf "%s" "$*"; }
  122. warn() { e "${YELLOW}Warning:$NORMAL" "$*"$'\n' >&2 ; }
  123. info() { e "${BLUE}II$NORMAL" "$*"$'\n' >&2 ; }
  124. verb() { [ -z "$VERBOSE" ] || e "$*"$'\n' >&2; }
  125. debug() { [ -z "$DEBUG" ] || e "$*"$'\n' >&2; }
  126. err() { e "${RED}Error:$NORMAL $*"$'\n' >&2 ; }
  127. die() { err "$@" ; exit 1; }
  128. ## equivalent of 'xargs echo' with builtins
  129. nspc() {
  130. local content
  131. content=$(printf "%s " $(cat -))
  132. printf "%s" "${content::-1}"
  133. }
  134. get_path() { (
  135. IFS=:
  136. for d in $PATH; do
  137. filename="$d/$1"
  138. [ -f "$filename" -a -x "$filename" ] && {
  139. echo "$d/$1"
  140. return 0
  141. }
  142. done
  143. return 1
  144. ) }
  145. depends() {
  146. ## Avoid colliding with variables that are created with depends.
  147. local __i __path __new_name
  148. for __i in "$@"; do
  149. if ! __path=$(get_path "$__i"); then
  150. __new_name="$(echo "${__i//-/_}")"
  151. if [ "$__new_name" != "$__i" ]; then
  152. depends "$__new_name"
  153. else
  154. err "dependency check: couldn't find '$__i' required command."
  155. exit 1
  156. fi
  157. else
  158. if ! test -z "$__path" ; then
  159. export "$(echo "${__i//- /__}")"="$__path"
  160. fi
  161. fi
  162. done
  163. }
  164. get_os() {
  165. local uname_output
  166. uname_output="$(uname -s)"
  167. case "${uname_output}" in
  168. Linux*)
  169. if [[ "$(< /proc/version)" =~ "@(Microsoft|WSL)" ]]; then
  170. e wsl
  171. # elif [[ "$(< /proc/version)" =~ "@(microsoft|WSL)" ]]; then
  172. # e wsl2
  173. else
  174. e linux
  175. fi
  176. ;;
  177. Darwin*) e mac;;
  178. CYGWIN*) e cygwin;;
  179. MINGW*) e mingw;;
  180. *) e "UNKNOWN:${uname_output}";;
  181. esac
  182. }
  183. read-0() {
  184. local eof= IFS=''
  185. while [ "$1" ]; do
  186. read -r -d '' -- "$1" || eof=1
  187. shift
  188. done
  189. [ -z "$eof" ]
  190. }
  191. read-0a() {
  192. local eof= IFS=''
  193. while [ "$1" ]; do
  194. IFS='' read -r -d $'\n' -- "$1" || eof=1
  195. shift
  196. done
  197. [ -z "$eof" ]
  198. }
  199. p0() {
  200. printf "%s\0" "$@"
  201. }
  202. cla.normalize() {
  203. local letters arg i
  204. while [ "$#" != 0 ]; do
  205. arg=$1
  206. case "$arg" in
  207. --)
  208. p0 "$@"
  209. return 0
  210. ;;
  211. --*=*|-*=*)
  212. shift
  213. set -- "${arg%%=*}" "${arg#*=}" "$@"
  214. continue
  215. ;;
  216. --*|-?) :;;
  217. -*)
  218. letters=${arg:1}
  219. shift
  220. i=${#letters}
  221. while ((i--)); do
  222. set -- -${letters:$i:1} "$@"
  223. done
  224. continue
  225. ;;
  226. esac
  227. p0 "$arg"
  228. shift
  229. done
  230. }
  231. docker_has_image() {
  232. local image="$1"
  233. images=$(docker images -q "$image" 2>/dev/null) || {
  234. err "docker images call has failed unexpectedly."
  235. return 1
  236. }
  237. [ -n "$images" ]
  238. }
  239. docker_image_id() {
  240. local image="$1"
  241. image_id=$(docker inspect "$image" --format='{{.Id}}') || return 1
  242. echo "$image_id"
  243. }
  244. ##
  245. ## Compose-core common functions
  246. ##
  247. list_compose_vars() {
  248. while read-0a def; do
  249. def="${def##* }"
  250. def="${def%=*}"
  251. p0 "$def"
  252. done < <(declare -p | grep "^declare -x COMPOSE_[A-Z_]\+=\"")
  253. }
  254. get_running_compose_containers() {
  255. ## XXXvlab: docker bug: there will be a final newline anyway
  256. docker ps --filter label="compose.service" --format='{{.ID}}'
  257. docker ps --filter label="compose" --format='{{.ID}}'
  258. }
  259. get_volumes_for_container() {
  260. local container="$1"
  261. docker inspect \
  262. --format '{{range $mount := .Mounts}}{{$mount.Source}}{{"\x00"}}{{$mount.Destination}}{{"\x00"}}{{end}}' \
  263. "$container"
  264. }
  265. is_volume_used() {
  266. local volume="$1"
  267. while read container_id; do
  268. while read-0 src dst; do
  269. [ "$src" == "$volume" ] && return 0
  270. done < <(get_volumes_for_container "$container_id")
  271. done < <(get_running_compose_containers)
  272. return 1
  273. }
  274. _MULTIOPTION_REGEX='^((-[a-zA-Z]|--[a-zA-Z0-9-]+)(, )?)+'
  275. _MULTIOPTION_REGEX_LINE_FILTER=$_MULTIOPTION_REGEX'(\s|=)'
  276. ##
  277. ## compose launcher functions
  278. ##
  279. clean_unused_sessions() {
  280. for f in "$SESSION_DIR/"*; do
  281. [ -e "$f" ] || continue
  282. is_volume_used "$f" && continue
  283. ## XXXvlab: the second rmdir should not be useful
  284. rm -f "$f" >/dev/null || rmdir "$f" >/dev/null || {
  285. debug "Unexpected session remnants $f"
  286. }
  287. done
  288. }
  289. check_no_links_subdirs() {
  290. local dir
  291. for dir in "$@"; do
  292. [ -d "$dir" ] || continue
  293. if [ -L "$dir" ]; then
  294. err "Unfortunately, this compose launcher do not support yet symlinks in charm-store."
  295. echo " Found symlink in charm-store: $dir" >&2
  296. return 1
  297. fi
  298. [ -e "$dir/metadata.yml" ] && continue
  299. check_no_links_subdirs "$dir"/* || return 1
  300. done
  301. }
  302. get_override() {
  303. local override
  304. override=$(get_volume_opt "$@") || return 1
  305. if [ -n "$override" ]; then
  306. if ! [ -f "$override" ]; then
  307. err "Invalid override of 'compose-core' detected." \
  308. "File '$override' does not exist on host."
  309. return 1
  310. fi
  311. echo "$override"
  312. fi
  313. }
  314. get_hash_image() {
  315. local compose_docker_image="$1" override="$2"
  316. {
  317. docker_image_id "$compose_docker_image" || {
  318. err "Failed to get docker image id of image '$compose_docker_image'."
  319. return 1
  320. }
  321. p0 ""
  322. [ -n "$override" ] && cat "$override"
  323. true
  324. } | hash_get
  325. return "${PIPESTATUS[0]}"
  326. }
  327. get_compose_file_opt() {
  328. local hash_bin="$1" override="$2" \
  329. cache_file="$COMPOSE_LAUNCHER_CACHE/$FUNCNAME.cache.$(p0 "$@" | hash_get)"
  330. if [ -e "$cache_file" ]; then
  331. cat "$cache_file" &&
  332. touch "$cache_file" || return 1
  333. return 0
  334. fi
  335. shift 2
  336. DC_MATCH_MULTI=$(get_compose_multi_opts_list "$hash_bin" "$override") || return 1
  337. DC_MATCH_SINGLE=$(get_compose_single_opts_list "$hash_bin" "$override") || return 1
  338. while read-0 arg; do
  339. case "$arg" in
  340. "-f"|"--file")
  341. read-0 value
  342. e "$value"
  343. return 0
  344. ;;
  345. --*|-*)
  346. if str_pattern_matches "$arg" $DC_MATCH_MULTI; then
  347. read-0 value
  348. opts+=("$arg" "$value")
  349. shift
  350. elif str_pattern_matches "$arg" $DC_MATCH_SINGLE; then
  351. opts+=("$arg")
  352. else
  353. debug "Unknown option '$arg'. Didn't manage to pre-parse correctly options."
  354. return 1
  355. fi
  356. ;;
  357. *)
  358. return 1
  359. ;;
  360. esac
  361. done < <(cla.normalize "$@") | tee "$cache_file"
  362. }
  363. replace_compose_file_opt() {
  364. local hash_bin="$1" override="$2" args arg \
  365. cache_file="$COMPOSE_LAUNCHER_CACHE/$FUNCNAME.cache.$(p0 "$@" | hash_get)"
  366. if [ -e "$cache_file" ]; then
  367. cat "$cache_file" &&
  368. touch "$cache_file" || return 1
  369. return 0
  370. fi
  371. debug "Replacing '-f|--file' argument in command line."
  372. shift 2
  373. DC_MATCH_MULTI=$(get_compose_multi_opts_list "$hash_bin" "$override") || return 1
  374. DC_MATCH_SINGLE=$(get_compose_single_opts_list "$hash_bin" "$override") || return 1
  375. args=()
  376. while read-0 arg; do
  377. case "$arg" in
  378. "-f"|"--file")
  379. read-0 value
  380. args+=("$arg" "${value##*/}")
  381. ;;
  382. --*|-*)
  383. if str_pattern_matches "$arg" $DC_MATCH_MULTI; then
  384. read-0 value
  385. args+=("$arg" "$value")
  386. shift
  387. elif str_pattern_matches "$arg" $DC_MATCH_SINGLE; then
  388. args+=("$arg")
  389. else
  390. err "Unknown option '$arg'. Didn't manage to pre-parse correctly options."
  391. return 1
  392. fi
  393. ;;
  394. *)
  395. args+=("$arg")
  396. while read-0 arg; do
  397. args+=("$arg")
  398. done
  399. ;;
  400. esac
  401. done < <(cla.normalize "$@")
  402. p0 "${args[@]}" | tee "$cache_file"
  403. }
  404. get_compose_opts_list() {
  405. local hash_bin="$1" override="$2" \
  406. cache_file="$COMPOSE_LAUNCHER_CACHE/$FUNCNAME.cache.$1"
  407. if [ -e "$cache_file" ]; then
  408. cat "$cache_file" &&
  409. touch "$cache_file" || return 1
  410. return 0
  411. fi
  412. debug "Pre-Launching docker to retrieve command line argument definitions."
  413. opts_list=()
  414. if [ -n "$override" ]; then
  415. opts_list+=("-v" "$override:/usr/local/bin/compose-core:ro")
  416. fi
  417. compose_opts_help=$(docker run "${opts_list[@]}" "$COMPOSE_DOCKER_IMAGE" --help 2>/dev/null)
  418. echo "$compose_opts_help" |
  419. grep '^Options:' -A 20000 |
  420. tail -n +2 |
  421. { cat ; echo; } |
  422. grep -E -m 1 "^\S*\$" -B 10000 |
  423. head -n -1 |
  424. grep -E "^\s+-" |
  425. sed_compat 's/\s+((((-[a-zA-Z]|--[a-zA-Z0-9-]+)( [A-Z=]+|=[^ ]+)?)(, )?)+)\s+.*$/\1/g' |
  426. tee "$cache_file" || return 1
  427. }
  428. multi_opts_filter() {
  429. grep -E "$_MULTIOPTION_REGEX_LINE_FILTER" |
  430. sed_compat "s/^($_MULTIOPTION_REGEX)(\s|=).*$/\1/g" |
  431. tr ',' "\n" | nspc
  432. }
  433. single_opts_filter() {
  434. grep -E -v "$_MULTIOPTION_REGEX_LINE_FILTER" |
  435. tr ',' "\n" | nspc
  436. }
  437. get_compose_multi_opts_list() {
  438. local hash_bin="$1" override="$2" \
  439. cache_file="$COMPOSE_LAUNCHER_CACHE/$FUNCNAME.cache.$1" opts_list
  440. if [ -e "$cache_file" ]; then
  441. cat "$cache_file" &&
  442. touch "$cache_file" || return 1
  443. return 0
  444. fi
  445. opts_list=$(get_compose_opts_list "$hash_bin" "$override") || return 1
  446. echo "$opts_list" | multi_opts_filter | tee "$cache_file"
  447. }
  448. get_compose_single_opts_list() {
  449. local hash_bin="$1" override="$2" \
  450. cache_file="$COMPOSE_LAUNCHER_CACHE/$FUNCNAME.cache.$1" opts_list
  451. if [ -e "$cache_file" ]; then
  452. cat "$cache_file" &&
  453. touch "$cache_file" || return 1
  454. return 0
  455. fi
  456. opts_list=$(get_compose_opts_list "$hash_bin" "$override") || return 1
  457. echo "$opts_list" | single_opts_filter | tee "$cache_file"
  458. }
  459. get_volume_opt() {
  460. local cache_file="$COMPOSE_LAUNCHER_CACHE/$FUNCNAME.cache.$(p0 "$@" | hash_get)"
  461. if [ -e "$cache_file" ]; then
  462. cat "$cache_file" &&
  463. touch "$cache_file" || return 1
  464. return 0
  465. fi
  466. while [ "$#" != 0 ]; do
  467. case "$1" in
  468. "-v")
  469. dst="${2#*:}"
  470. dst="${dst%:*}"
  471. if [ "$dst" == "/usr/local/bin/compose-core" ]; then
  472. override="${2%%:*}"
  473. debug "Override of compose-core found: $override"
  474. fi
  475. shift;;
  476. "-e"|"-w")
  477. shift;;
  478. *)
  479. :
  480. ;;
  481. esac
  482. shift
  483. done
  484. { [ -n "$override" ] && echo "$override"; } | tee "$cache_file"
  485. }
  486. get_tz() {
  487. if [ -n "$TZ" ]; then
  488. :
  489. elif [ -n "$COMPOSE_LOCAL_ROOT" ] && ## previous compose run
  490. [ -e "$COMPOSE_LOCAL_ROOT/etc/timezone" ]; then
  491. read -r TZ < "$COMPOSE_LOCAL_ROOT/etc/timezone"
  492. elif [ -e "/etc/timezone" ]; then ## debian host system timezone
  493. read -r TZ < /etc/timezone
  494. elif [ -e "/etc/localtime" ]; then ## redhat and macosx sys timezone
  495. local fullpath dirname
  496. fullpath="$(readlink -f /etc/localtime)"
  497. dirname="${fullpath%/*}"
  498. TZ=${TZ:-${fullpath##*/}/${dirname##*/}}
  499. else
  500. err "Timezone not found nor inferable !"
  501. echo " compose relies on '/etc/timezone' or '/etc/localtime' to be present " >&2
  502. echo " so as to ensure same timezone for all containers that need it." >&2
  503. echo >&2
  504. echo " You can set a default value for compose by create issuing:" >&2
  505. echo >&2
  506. if [ -n "$COMPOSE_LOCAL_ROOT" ] && [ "$UID" != 0 ]; then
  507. echo " mkdir -p $COMPOSE_LOCAL_ROOT/etc &&" >&2
  508. echo " echo \"Europe/Paris\" > $COMPOSE_LOCAL_ROOT/etc/timezone" >&2
  509. else
  510. echo " echo \"Europe/Paris\" > /etc/timezone" >&2
  511. fi
  512. echo >&2
  513. echo " Of course, you can change 'Europe/Paris' value by any other valid timezone." >&2
  514. echo " timezone." >&2
  515. echo >&2
  516. echo " Notice you can also use \$TZ environment variable, but the value" >&2
  517. echo " will be necessary each time you launch compose." >&2
  518. echo >&2
  519. return 1
  520. fi
  521. e "$TZ"
  522. }
  523. pretty_print() {
  524. while [ "$#" != 0 ]; do
  525. case "$1" in
  526. "-v"|"-e"|"-w")
  527. e "$1" "$2" "\\"$'\n'
  528. shift;;
  529. *)
  530. e "$1 ";;
  531. esac
  532. shift
  533. done
  534. }
  535. win_env() { "$CMD" /c "<nul set /p=%${1}%" 2>/dev/null; }
  536. wsl_path_env() { wslpath "$(win_env "${1}")"; }
  537. set_os() {
  538. OS="$(get_os)"
  539. case "$OS" in
  540. linux)
  541. ## Order matters, files get to override vars
  542. compose_config_files=(
  543. ## DEFAULT LINUX VARS
  544. /etc/default/charm
  545. /etc/default/datastore
  546. /etc/default/compose
  547. ## COMPOSE SYSTEM-WIDE FILES
  548. /etc/compose.conf
  549. /etc/compose.local.conf
  550. /etc/compose/local.conf
  551. ## COMPOSE USER FILES
  552. ~/.compose/etc/local.conf
  553. ~/.compose.conf
  554. )
  555. COMPOSE_LOCAL_ROOT=${COMPOSE_LOCAL_ROOT:-"$HOME/.compose"}
  556. COMPOSE_VAR=${COMPOSE_VAR:-/var/lib/compose}
  557. COMPOSE_CACHE=${COMPOSE_CACHE:-/var/cache/compose}
  558. DATASTORE=${DATASTORE:-/srv/datastore/data}
  559. CONFIGSTORE=${CONFIGSTORE:-/srv/datastore/config}
  560. if [ "$UID" == 0 ]; then
  561. SESSION_DIR=${SESSION_DIR:-"$COMPOSE_VAR"/sessions}
  562. CHARM_STORE=${CHARM_STORE:-/srv/charm-store}
  563. TZ_PATH=${TZ_PATH:-"$COMPOSE_VAR"/timezones}
  564. COMPOSE_LAUNCHER_CACHE=${COMPOSE_LAUNCHER_CACHE:-"$COMPOSE_CACHE"}
  565. else
  566. SESSION_DIR=${SESSION_DIR:-"$COMPOSE_LOCAL_ROOT"/sessions}
  567. CHARM_STORE=${CHARM_STORE:-"$HOME"/.charm-store}
  568. TZ_PATH=${TZ_PATH:-"$COMPOSE_LOCAL_ROOT"/timezones}
  569. COMPOSE_LAUNCHER_CACHE=${COMPOSE_LAUNCHER_CACHE:-"$COMPOSE_LOCAL_ROOT"/cache}
  570. fi
  571. ;;
  572. mac)
  573. ## Order matters, files get to override vars
  574. compose_config_files=(
  575. ## COMPOSE SYSTEM-WIDE FILES
  576. /etc/compose.conf
  577. /etc/compose.local.conf
  578. /etc/compose/local.conf
  579. ## COMPOSE USER FILES
  580. ~/.compose/etc/local.conf
  581. ~/.compose.conf
  582. )
  583. COMPOSE_LOCAL_ROOT=${COMPOSE_LOCAL_ROOT:-"$HOME/.compose"}
  584. COMPOSE_VAR=${COMPOSE_VAR:-"$COMPOSE_LOCAL_ROOT"/lib}
  585. COMPOSE_CACHE=${COMPOSE_CACHE:-"$COMPOSE_LOCAL_ROOT"/cache}
  586. SESSION_DIR=${SESSION_DIR:-"$COMPOSE_LOCAL_ROOT"/sessions}
  587. DATASTORE=${DATASTORE:-"$COMPOSE_LOCAL_ROOT"/data}
  588. CONFIGSTORE=${CONFIGSTORE:-"$COMPOSE_LOCAL_ROOT"/config}
  589. CHARM_STORE=${CHARM_STORE:-"$HOME"/.charm-store}
  590. TZ_PATH=${TZ_PATH:-"$COMPOSE_LOCAL_ROOT"/timezones}
  591. COMPOSE_LAUNCHER_CACHE=${COMPOSE_LAUNCHER_CACHE:-"$COMPOSE_LOCAL_ROOT"/cache}
  592. ;;
  593. wsl)
  594. type -p cmd.exe >/dev/null || {
  595. die "cmd.exe is not found in \$PATH."
  596. }
  597. CMD=$(type -p cmd.exe >/dev/null) || {
  598. for p in {/mnt,}/c/WINDOWS/SYSTEM32; do
  599. if [ -x "$p"/cmd.exe ]; then
  600. CMD="$p"/cmd.exe
  601. fi
  602. done
  603. if [ -z "$CMD" ]; then
  604. die "cmd.exe is not found in \$PATH." \
  605. "And could not find it in standard directories."
  606. fi
  607. }
  608. WIN_HOME="$(wsl_path_env UserProfile)"
  609. WIN_PROGRAM_FILES="$(wsl_path_env ProgramFiles)"
  610. ## Order matters, files get to override vars
  611. compose_config_files=(
  612. ## COMPOSE SYSTEM-WIDE FILES
  613. /etc/compose.conf
  614. /etc/compose.local.conf
  615. /etc/compose/local.conf
  616. ## COMPOSE USER FILES
  617. ~/.compose/etc/local.conf
  618. ~/.compose.conf
  619. ## COMPOSE USER FILES
  620. {~,"$WIN_HOME"}/.compose/etc/local.conf
  621. {~,"$WIN_HOME"}/.compose.conf
  622. )
  623. APPDATA_BASE="$WIN_HOME/AppData/Local/Compose"
  624. COMPOSE_LAUNCHER_APP_DIR="$WIN_PROGRAM_FILES/Compose"
  625. COMPOSE_LOCAL_ROOT=${COMPOSE_LOCAL_ROOT:-"$APPDATA_BASE/Launcher"}
  626. COMPOSE_VAR=${COMPOSE_VAR:-"$APPDATA_BASE/lib"}
  627. COMPOSE_CACHE=${COMPOSE_CACHE:-"$APPDATA_BASE/cache"}
  628. DATASTORE=${DATASTORE:-"$APPDATA_BASE/data"}
  629. CONFIGSTORE=${CONFIGSTORE:-"$APPDATA_BASE/config"}
  630. mkdir -p "$COMPOSE_VAR" "$COMPOSE_CACHE" "$DATASTORE" "$CONFIGSTORE" || return 1
  631. SESSION_DIR=${SESSION_DIR:-"$COMPOSE_LOCAL_ROOT"/sessions}
  632. CHARM_STORE=${CHARM_STORE:-"$COMPOSE_LAUNCHER_APP_DIR/charm-store"}
  633. TZ_PATH=${TZ_PATH:-"$COMPOSE_LOCAL_ROOT"/timezones}
  634. COMPOSE_LAUNCHER_CACHE=${COMPOSE_LAUNCHER_CACHE:-"$COMPOSE_LOCAL_ROOT"/cache}
  635. ;;
  636. *)
  637. echo "System '$os' not supported yet." >&2
  638. return 1
  639. ;;
  640. esac
  641. }
  642. mk_docker_run_options() {
  643. set_os || return 1
  644. docker_run_opts=("-v" "/var/run/docker.sock:/var/run/docker.sock")
  645. ##
  646. ## Load config files
  647. ##
  648. if [ -z "$DISABLE_SYSTEM_CONFIG_FILE" ]; then
  649. ## XXXvlab: should provide YML config opportunities in possible parent dirs ?
  650. ## userdir ? and global /etc/compose.yml ?
  651. for cfgfile in "${compose_config_files[@]}"; do
  652. [ -e "$cfgfile" ] || continue
  653. docker_run_opts+=("-v" "$cfgfile:$cfgfile:ro")
  654. debug "Loading config file '$cfgfile'."
  655. . "$cfgfile"
  656. done
  657. else
  658. docker_run_opts+=("-e" "DISABLE_SYSTEM_CONFIG_FILE=$DISABLE_SYSTEM_CONFIG_FILE")
  659. fi
  660. mkdir -p "$COMPOSE_LAUNCHER_CACHE" || return 1
  661. ## get TZ value and prepare TZ_PATH
  662. TZ=$(get_tz) || return 1
  663. mkdir -p "${TZ_PATH}"
  664. TZ_PATH="${TZ_PATH}/$(e "$TZ" | hash_get | cut -c 1-8)" || return 1
  665. [ -e "$TZ_PATH" ] || e "$TZ" > "$TZ_PATH"
  666. ## CACHE/DATA DIRS
  667. docker_run_opts+=("-v" "$COMPOSE_VAR:/var/lib/compose")
  668. docker_run_opts+=("-v" "$COMPOSE_CACHE:/var/cache/compose")
  669. docker_run_opts+=("-v" "$TZ_PATH:/etc/timezone:ro")
  670. ##
  671. ## Checking vars
  672. ##
  673. ## CHARM_STORE
  674. [ -e "$CHARM_STORE" ] || mkdir -p "$CHARM_STORE" || return 1
  675. [ -L "$CHARM_STORE" ] && {
  676. CHARM_STORE=$(readlink -f "$CHARM_STORE") || return 1
  677. }
  678. docker_run_opts+=(
  679. "-v" "$CHARM_STORE:/srv/charm-store:ro"
  680. "-e" "CHARM_STORE=/srv/charm-store"
  681. "-e" "HOST_CHARM_STORE=$CHARM_STORE"
  682. )
  683. check_no_links_subdirs "$CHARM_STORE"/* || return 1
  684. ## DATASTORE and CONFIGSTORE
  685. docker_run_opts+=(
  686. "-v" "$DATASTORE:/srv/datastore/data:rw"
  687. "-e" "DATASTORE=/srv/datastore/data"
  688. "-e" "HOST_DATASTORE=$DATASTORE"
  689. "-v" "$CONFIGSTORE:/srv/datastore/config:rw"
  690. "-e" "CONFIGSTORE=/srv/datastore/config"
  691. "-e" "HOST_CONFIGSTORE=$CONFIGSTORE"
  692. )
  693. if [ "$OS" == "linux" ]; then
  694. [ -d "$HOME/.docker" ] && \
  695. docker_run_opts+=("-v" "$HOME/.docker:/root/.docker:ro")
  696. [ -d "$HOME/.ssh" ] && \
  697. docker_run_opts+=("-v" "$HOME/.ssh:/root/.ssh:ro")
  698. [ -d "/etc/ssh" ] && \
  699. docker_run_opts+=("-v" "/etc/ssh:/etc/ssh:ro")
  700. fi
  701. COMPOSE_DOCKER_IMAGE=${COMPOSE_DOCKER_IMAGE:-docker.0k.io/compose}
  702. docker_run_opts+=("-e" "COMPOSE_DOCKER_IMAGE=$COMPOSE_DOCKER_IMAGE")
  703. if ! docker_has_image "$COMPOSE_DOCKER_IMAGE"; then
  704. docker pull "$COMPOSE_DOCKER_IMAGE" || return 1
  705. fi
  706. COMPOSE_LAUNCHER_BIN=$(readlink -f "${BASH_SOURCE[0]}")
  707. docker_run_opts+=("-v" "$COMPOSE_LAUNCHER_BIN:/usr/local/bin/compose")
  708. COMPOSE_LAUNCHER_BIN_OVERRIDE=$(get_override "${docker_run_opts[@]}") || return 1
  709. COMPOSE_LAUNCHER_HASH=$(
  710. get_hash_image "$COMPOSE_DOCKER_IMAGE" "$COMPOSE_LAUNCHER_BIN_OVERRIDE") || return 1
  711. while read-0 var; do
  712. case "$var" in
  713. COMPOSE_YML_FILE|COMPOSE_LAUNCHER_BIN|COMPOSE_DOCKER_IMAGE|\
  714. COMPOSE_LAUNCHER_OPTS|COMPOSE_VAR|COMPOSE_CACHE)
  715. :
  716. ;;
  717. *)
  718. docker_run_opts+=("-e" "$var=${!var}")
  719. ;;
  720. esac
  721. done < <(list_compose_vars)
  722. ARG_COMPOSE_FILE=$(
  723. get_compose_file_opt "$COMPOSE_LAUNCHER_HASH" "$COMPOSE_LAUNCHER_BIN_OVERRIDE" \
  724. "$@") || return 1
  725. compose_file="${ARG_COMPOSE_FILE:-$COMPOSE_YML_FILE}"
  726. if [ -z "$compose_file" ]; then
  727. ## Find a compose.yml in parents
  728. debug "No config file specified on command line arguments"
  729. debug "Looking for 'compose.yml' in self and parents.."
  730. if parent=$(while true; do
  731. [ -e "./compose.yml" ] && {
  732. echo "$PWD"
  733. exit 0
  734. }
  735. [ "$PWD" == "/" ] && return 1
  736. cd ..
  737. done
  738. ); then
  739. compose_file="$(realpath "$parent"/"compose.yml")"
  740. debug " .. found '$compose_file'"
  741. else
  742. debug " .. not found."
  743. fi
  744. fi
  745. if [ -z "$compose_file" ] && [ "${DEFAULT_COMPOSE_FILE+x}" ]; then
  746. debug "Using \$DEFAULT_COMPOSE_FILE value '$DEFAULT_COMPOSE_FILE' as compose file."
  747. compose_file="$DEFAULT_COMPOSE_FILE"
  748. fi
  749. if [ -n "$compose_file" ]; then
  750. if ! [ -f "$compose_file" ]; then
  751. die "Specified compose file '$compose_file' not found."
  752. fi
  753. compose_file="$(realpath "$compose_file")"
  754. if [ "$OS" == "wsl" ]; then
  755. ## Docker host is not same linux than WSL, so
  756. ## access to root files are not the same.
  757. ##YYYvlab, check on cp where is the base
  758. dst="$COMPOSE_LAUNCHER_CACHE/compose.$(hash_get < "$compose_file").yml"
  759. cp "$compose_file" "$dst"
  760. ## docker host start with /c/... whereas WSL could start with /mnt/c/...
  761. local="$dst"
  762. else
  763. local="$compose_file"
  764. fi
  765. parent_dir="${compose_file%/*}"
  766. docker_path=/var/lib/compose/root/${parent_dir##*/}/${compose_file##*/}
  767. docker_run_opts+=(
  768. "-e" "COMPOSE_YML_FILE=${compose_file##*/}"
  769. "-e" "HOST_COMPOSE_YML_FILE=${local}"
  770. "-v" "${local}:${docker_path}:ro"
  771. "-w" "${docker_path%/*}"
  772. )
  773. else
  774. docker_path=/var/lib/compose/root
  775. docker_run_opts+=(
  776. "-w" "${docker_path}"
  777. )
  778. fi
  779. clean_unused_sessions
  780. filename=$(mktemp -p /tmp/ -t launch_opts-XXXXXXXXXXXXXXXX)
  781. p0 "${docker_run_opts[@]}" > "$filename"
  782. hash=$(hash_get < "$filename") || return 1
  783. src="$SESSION_DIR/$UID-$hash"
  784. dest="/var/lib/compose/sessions/$UID-$hash"
  785. additional_docker_run_opts=(
  786. "-v" "$SESSION_DIR/$UID-$hash:$dest:ro"
  787. "-e" "HOST_COMPOSE_LAUNCHER_OPTS=$SESSION_DIR/$UID-$hash"
  788. "-e" "COMPOSE_LAUNCHER_OPTS=$dest"
  789. "-e" "COMPOSE_LAUNCHER_BIN=$COMPOSE_LAUNCHER_BIN"
  790. "--label" "compose=1"
  791. )
  792. p0 "${additional_docker_run_opts[@]}" >> "$filename"
  793. docker_run_opts+=("${additional_docker_run_opts[@]}")
  794. ## keep also some env vars:
  795. for var in ARG_COMPOSE_FILE COMPOSE_DOCKER_IMAGE COMPOSE_LAUNCHER_{BIN_OVERRIDE,HASH}; do
  796. p0 "!env:$var=${!var}"
  797. done >> "$filename"
  798. mkdir -p "$SESSION_DIR" || return 1
  799. mv -f "$filename" "$src" || return 1
  800. }
  801. load_env() {
  802. docker_run_opts=()
  803. if [ -z "$COMPOSE_LAUNCHER_OPTS" ]; then
  804. mk_docker_run_options "$@" || return 1
  805. else
  806. set_os || return 1
  807. while read-0 opt; do
  808. if [[ "$opt" == "!env:"* ]]; then
  809. opt="${opt##!env:}"
  810. var="${opt%%=*}"
  811. value="${opt#*=}"
  812. debug "Loading var: $var=$value"
  813. export "$var"="$value"
  814. else
  815. docker_run_opts+=("$opt")
  816. fi
  817. done < <(cat "$COMPOSE_LAUNCHER_OPTS")
  818. fi
  819. }
  820. show_env() {
  821. echo "${WHITE}Environment:${NORMAL}"
  822. echo " COMPOSE_DOCKER_IMAGE: $COMPOSE_DOCKER_IMAGE"
  823. echo " CHARM_STORE: $CHARM_STORE"
  824. echo " DATASTORE: $DATASTORE"
  825. echo " CONFIGSTORE: $CONFIGSTORE"
  826. echo " COMPOSE_VAR: $COMPOSE_VAR"
  827. echo " COMPOSE_CACHE: $COMPOSE_CACHE"
  828. echo " COMPOSE_LAUNCHER_CACHE: $COMPOSE_LAUNCHER_CACHE"
  829. echo " SESSION_DIR: $SESSION_DIR"
  830. echo " TZ_PATH: $TZ_PATH"
  831. }
  832. run() {
  833. local os docker_run_opts
  834. load_env "$@" || return 1
  835. [ -n "$DEBUG" ] && show_env >&2
  836. if [ -n "$ARG_COMPOSE_FILE" ]; then
  837. array_read-0 cmd_args < \
  838. <(replace_compose_file_opt "$COMPOSE_LAUNCHER_HASH" \
  839. "$COMPOSE_LAUNCHER_BIN_OVERRIDE" \
  840. "$@")
  841. set -- "${cmd_args[@]}"
  842. fi
  843. ## XXXvlab: can't see a place where we wouldn't want to link stdin
  844. ## to internal process be it a terminal or not.
  845. docker_run_opts+=("-i")
  846. ## If stdin is a not a tty, then adding ``-t`` will fail
  847. [ -t 0 -a -t 1 ] && docker_run_opts+=("-t")
  848. debug "${WHITE}Launching:${NORMAL}"
  849. if [ -n "$DEBUG" ] || [ -n "$DRY_RUN" ]; then
  850. echo "docker run --rm \\"
  851. pretty_print "${docker_run_opts[@]}" | sed_compat 's/^/ /g;s/([^\])$/\1\\\n/g'
  852. if [ -z "$ENTER" ]; then
  853. printf "%s\n" " ${COMPOSE_DOCKER_IMAGE} \\"
  854. printf " "
  855. printf "%s " "$@"
  856. printf "\n"
  857. else
  858. echo " --entrypoint bash \\"
  859. echo " ${COMPOSE_DOCKER_IMAGE}"
  860. fi
  861. fi | { if [ -n "$DEBUG" ]; then sed_compat 's/^/ /g'; else cat; fi } >&2
  862. if [ -z "$DRY_RUN" ]; then
  863. debug "${WHITE}Execution:${NORMAL}"
  864. if [ -z "$ENTER" ]; then
  865. exec docker run --rm "${docker_run_opts[@]}" "${COMPOSE_DOCKER_IMAGE}" "$@"
  866. else
  867. exec docker run --rm "${docker_run_opts[@]}" \
  868. --entrypoint bash \
  869. "${COMPOSE_DOCKER_IMAGE}"
  870. fi
  871. fi
  872. }
  873. [ -n "$SOURCED" ] && return 0
  874. ##
  875. ## Code
  876. ##
  877. depends docker cat readlink sed realpath tee sed grep tail
  878. ansi_color "${ansi_color:-tty}"
  879. if [ "$SHOW_ENV" ]; then
  880. load_env "$@" || return 1
  881. show_env >&2
  882. exit 0
  883. fi
  884. if [ "$SHOW_CONFIG_LOCATIONS" ]; then
  885. set_os || return 1
  886. echo "compose will read these files if existing in the given order:"
  887. for loc in "${compose_config_files[@]}"; do
  888. echo " - $loc"
  889. done
  890. exit 0
  891. fi
  892. run "$@"