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.

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