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.

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