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.

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