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.

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