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.

690 lines
17 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
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*) e linux;;
  176. Darwin*) e mac;;
  177. CYGWIN*) e cygwin;;
  178. MINGW*) e mingw;;
  179. *) e "UNKNOWN:${uname_output}";;
  180. esac
  181. }
  182. read-0() {
  183. local eof= IFS=''
  184. while [ "$1" ]; do
  185. read -r -d '' -- "$1" || eof=1
  186. shift
  187. done
  188. [ -z "$eof" ]
  189. }
  190. read-0a() {
  191. local eof= IFS=''
  192. while [ "$1" ]; do
  193. IFS='' read -r -d $'\n' -- "$1" || eof=1
  194. shift
  195. done
  196. [ -z "$eof" ]
  197. }
  198. p0() {
  199. printf "%s\0" "$@"
  200. }
  201. cla.normalize() {
  202. local letters arg i
  203. while [ "$#" != 0 ]; do
  204. arg=$1
  205. case "$arg" in
  206. --)
  207. p0 "$@"
  208. return 0
  209. ;;
  210. --*=*|-*=*)
  211. shift
  212. set -- "${arg%%=*}" "${arg#*=}" "$@"
  213. continue
  214. ;;
  215. --*|-?) :;;
  216. -*)
  217. letters=${arg:1}
  218. shift
  219. i=${#letters}
  220. while ((i--)); do
  221. set -- -${letters:$i:1} "$@"
  222. done
  223. continue
  224. ;;
  225. esac
  226. p0 "$arg"
  227. shift
  228. done
  229. }
  230. docker_has_image() {
  231. local image="$1"
  232. images=$(docker images -q "$image" 2>/dev/null) || {
  233. err "docker images call has failed unexpectedly."
  234. return 1
  235. }
  236. [ -n "$images" ]
  237. }
  238. docker_image_id() {
  239. local image="$1"
  240. image_id=$(docker inspect "$image" --format='{{.Id}}') || return 1
  241. echo "$image_id"
  242. }
  243. ##
  244. ## Compose-core common functions
  245. ##
  246. list_compose_vars() {
  247. while read-0a def; do
  248. def="${def##* }"
  249. def="${def%=*}"
  250. p0 "$def"
  251. done < <(declare -p | grep "^declare -x COMPOSE_[A-Z_]\+=\"")
  252. }
  253. get_running_compose_containers() {
  254. ## XXXvlab: docker bug: there will be a final newline anyway
  255. docker ps --filter label="compose.service" --format='{{.ID}}'
  256. }
  257. get_volumes_for_container() {
  258. local container="$1"
  259. docker inspect \
  260. --format '{{range $mount := .Mounts}}{{$mount.Source}}{{"\x00"}}{{$mount.Destination}}{{"\x00"}}{{end}}' \
  261. "$container"
  262. }
  263. is_volume_used() {
  264. local volume="$1"
  265. while read container_id; do
  266. while read-0 src dst; do
  267. [ "$src" == "$volume" ] && return 0
  268. done < <(get_volumes_for_container "$container_id")
  269. done < <(get_running_compose_containers)
  270. return 1
  271. }
  272. _MULTIOPTION_REGEX='^((-[a-zA-Z]|--[a-zA-Z0-9-]+)(, )?)+'
  273. _MULTIOPTION_REGEX_LINE_FILTER=$_MULTIOPTION_REGEX'(\s|=)'
  274. ##
  275. ## compose launcher functions
  276. ##
  277. clean_unused_sessions() {
  278. for f in "$SESSION_DIR/"*; do
  279. [ -e "$f" ] || continue
  280. is_volume_used "$f" && continue
  281. ## XXXvlab: the second rmdir should not be useful
  282. rm -f "$f" >/dev/null || rmdir "$f" >/dev/null || {
  283. debug "Unexpected session remnants $f"
  284. }
  285. done
  286. }
  287. check_no_links_subdirs() {
  288. local dir
  289. for dir in "$@"; do
  290. [ -d "$dir" ] || continue
  291. if [ -L "$dir" ]; then
  292. err "Unfortunately, this compose launcher do not support yet symlinks in charm-store."
  293. echo " Found symlink in charm-store: $dir" >&2
  294. return 1
  295. fi
  296. [ -e "$dir/metadata.yml" ] && continue
  297. check_no_links_subdirs "$dir"/* || return 1
  298. done
  299. }
  300. get_tz() {
  301. if [ -n "$TZ" ]; then
  302. :
  303. elif [ -n "$COMPOSE_LOCAL_ROOT" ] && ## previous compose run
  304. [ -e "$COMPOSE_LOCAL_ROOT/etc/timezone" ]; then
  305. read -r TZ < "$COMPOSE_LOCAL_ROOT/etc/timezone"
  306. elif [ -e "/etc/timezone" ]; then ## debian host system timezone
  307. read -r TZ < /etc/timezone
  308. elif [ -e "/etc/localtime" ]; then ## redhat and macosx sys timezone
  309. local fullpath dirname
  310. fullpath="$(readlink -f /etc/localtime)"
  311. dirname="${fullpath%/*}"
  312. TZ=${TZ:-${fullpath##*/}/${dirname##*/}}
  313. else
  314. err "Timezone not found nor inferable !"
  315. echo " compose relies on '/etc/timezone' or '/etc/localtime' to be present " >&2
  316. echo " so as to ensure same timezone for all containers that need it." >&2
  317. echo >&2
  318. echo " You can set a default value for compose by create issuing:" >&2
  319. echo >&2
  320. if [ -n "$COMPOSE_LOCAL_ROOT" ] && [ "$UID" != 0 ]; then
  321. echo " mkdir -p $COMPOSE_LOCAL_ROOT/etc &&" >&2
  322. echo " echo \"Europe/Paris\" > $COMPOSE_LOCAL_ROOT/etc/timezone" >&2
  323. else
  324. echo " echo \"Europe/Paris\" > /etc/timezone" >&2
  325. fi
  326. echo >&2
  327. echo " Of course, you can change 'Europe/Paris' value by any other valid timezone." >&2
  328. echo " timezone." >&2
  329. echo >&2
  330. echo " Notice you can also use \$TZ environment variable, but the value" >&2
  331. echo " will be necessary each time you launch compose." >&2
  332. echo >&2
  333. return 1
  334. fi
  335. e "$TZ"
  336. }
  337. pretty_print() {
  338. while [ "$#" != 0 ]; do
  339. case "$1" in
  340. "-v"|"-e"|"-w")
  341. e "$1" "$2" "\\"$'\n'
  342. shift;;
  343. *)
  344. e "$1 ";;
  345. esac
  346. shift
  347. done
  348. }
  349. mk_docker_run_options() {
  350. ## Order matters, files get to override vars
  351. compose_config_files=(
  352. ## DEFAULT LINUX VARS
  353. /etc/default/charm
  354. /etc/default/datastore
  355. /etc/default/compose
  356. ## COMPOSE SYSTEM-WIDE FILES
  357. /etc/compose.conf
  358. /etc/compose.local.conf
  359. /etc/compose/local.conf
  360. ## COMPOSE USER FILES
  361. ~/.compose/etc/local.conf
  362. ~/.compose.conf
  363. )
  364. docker_run_opts=("-v" "/var/run/docker.sock:/var/run/docker.sock")
  365. ## current dir
  366. if parent=$(while true; do
  367. [ -e "./compose.yml" ] && {
  368. echo "$PWD"
  369. exit 0
  370. }
  371. [ "$PWD" == "/" ] && exit 1
  372. cd ..
  373. done
  374. ); then
  375. docker_path=$COMPOSE_VAR/root/$(basename "$parent")
  376. docker_run_opts+=("-v" "$parent:$docker_path:ro" \
  377. "-w" "$docker_path")
  378. fi
  379. ##
  380. ## Load config files
  381. ##
  382. if [ -z "$DISABLE_SYSTEM_CONFIG_FILE" ]; then
  383. ## XXXvlab: should provide YML config opportunities in possible parent dirs ?
  384. ## userdir ? and global /etc/compose.yml ?
  385. for cfgfile in "${compose_config_files[@]}"; do
  386. [ -e "$cfgfile" ] || continue
  387. docker_run_opts+=("-v" "$cfgfile:$cfgfile:ro")
  388. . "$cfgfile"
  389. done
  390. else
  391. docker_run_opts+=("-e" "DISABLE_SYSTEM_CONFIG_FILE=$DISABLE_SYSTEM_CONFIG_FILE")
  392. fi
  393. COMPOSE_LOCAL_ROOT=${COMPOSE_LOCAL_ROOT:-"$HOME/.compose"}
  394. case "$(get_os)" in
  395. linux)
  396. COMPOSE_VAR=${COMPOSE_VAR:-/var/lib/compose}
  397. COMPOSE_CACHE=${COMPOSE_CACHE:-/var/cache/compose}
  398. DATASTORE=${DATASTORE:-/srv/datastore/data}
  399. CONFIGSTORE=${CONFIGSTORE:-/srv/datastore/config}
  400. if [ "$UID" == 0 ]; then
  401. SESSION_DIR=${SESSION_DIR:-"$COMPOSE_VAR"/sessions}
  402. CHARM_STORE=${CHARM_STORE:-/srv/charm-store}
  403. TZ_PATH=${TZ_PATH:-"$COMPOSE_VAR"/timezones}
  404. else
  405. SESSION_DIR=${SESSION_DIR:-"$COMPOSE_LOCAL_ROOT"/sessions}
  406. CHARM_STORE=${CHARM_STORE:-"$HOME"/.charm-store}
  407. TZ_PATH=${TZ_PATH:-"$COMPOSE_LOCAL_ROOT"/timezones}
  408. fi
  409. ;;
  410. mac)
  411. COMPOSE_VAR=${COMPOSE_VAR:-"$COMPOSE_LOCAL_ROOT"/lib}
  412. COMPOSE_CACHE=${COMPOSE_CACHE:-"$COMPOSE_LOCAL_ROOT"/cache}
  413. SESSION_DIR=${SESSION_DIR:-"$COMPOSE_LOCAL_ROOT"/sessions}
  414. DATASTORE=${DATASTORE:-"$COMPOSE_LOCAL_ROOT"/data}
  415. CONFIGSTORE=${CONFIGSTORE:-"$COMPOSE_LOCAL_ROOT"/config}
  416. CHARM_STORE=${CHARM_STORE:-"$HOME"/.charm-store}
  417. TZ_PATH=${TZ_PATH:-"$COMPOSE_LOCAL_ROOT"/timezones}
  418. ;;
  419. *)
  420. echo "System '$os' not supported yet." >&2
  421. exit 1
  422. ;;
  423. esac
  424. ## get TZ value and prepare TZ_PATH
  425. TZ=$(get_tz) || exit 1
  426. mkdir -p "${TZ_PATH}"
  427. TZ_PATH="${TZ_PATH}/$(e "$TZ" | sha256sum | cut -c 1-8)" || exit 1
  428. [ -e "$TZ_PATH" ] || e "$TZ" > "$TZ_PATH"
  429. ## CACHE/DATA DIRS
  430. docker_run_opts+=("-v" "$COMPOSE_VAR:/var/lib/compose")
  431. docker_run_opts+=("-v" "$COMPOSE_CACHE:/var/cache/compose")
  432. docker_run_opts+=("-v" "$TZ_PATH:/etc/timezone:ro")
  433. ##
  434. ## Checking vars
  435. ##
  436. ## CHARM_STORE
  437. [ -e "$CHARM_STORE" ] || mkdir -p "$CHARM_STORE" || exit 1
  438. [ -L "$CHARM_STORE" ] && {
  439. CHARM_STORE=$(readlink -f "$CHARM_STORE") || exit 1
  440. }
  441. docker_run_opts+=(
  442. "-v" "$CHARM_STORE:/srv/charm-store:ro"
  443. "-e" "CHARM_STORE=/srv/charm-store"
  444. "-e" "HOST_CHARM_STORE=$CHARM_STORE"
  445. )
  446. check_no_links_subdirs "$CHARM_STORE"/* || exit 1
  447. ## DEFAULT_COMPOSE_FILE
  448. if [ "${DEFAULT_COMPOSE_FILE+x}" ]; then
  449. DEFAULT_COMPOSE_FILE=$(realpath "$DEFAULT_COMPOSE_FILE")
  450. dirname=$(dirname "$DEFAULT_COMPOSE_FILE")/
  451. if [ -e "${DEFAULT_COMPOSE_FILE}" ]; then
  452. docker_run_opts+=("-v" "$dirname:$dirname:ro")
  453. fi
  454. fi
  455. ## COMPOSE_YML_FILE
  456. if [ "${COMPOSE_YML_FILE+x}" ]; then
  457. if [ -e "${COMPOSE_YML_FILE}" ]; then
  458. docker_run_opts+=(
  459. "-v" "$COMPOSE_YML_FILE:/tmp/compose.yml:ro"
  460. "-e" "COMPOSE_YML_FILE=/tmp/compose.yml"
  461. "-e" "HOST_COMPOSE_YML_FILE=/tmp/compose.yml"
  462. )
  463. fi
  464. fi
  465. ## DATASTORE and CONFIGSTORE
  466. docker_run_opts+=(
  467. "-v" "$DATASTORE:/srv/datastore/data:rw"
  468. "-e" "DATASTORE=/srv/datastore/data"
  469. "-e" "HOST_DATASTORE=$DATASTORE"
  470. "-v" "$CONFIGSTORE:/srv/datastore/config:rw"
  471. "-e" "CONFIGSTORE=/srv/datastore/config"
  472. "-e" "HOST_CONFIGSTORE=$CONFIGSTORE"
  473. )
  474. docker_run_opts+=("-v" "$HOME/.docker:/root/.docker")
  475. COMPOSE_DOCKER_IMAGE=${COMPOSE_DOCKER_IMAGE:-docker.0k.io/compose}
  476. docker_run_opts+=("-e" "COMPOSE_DOCKER_IMAGE=$COMPOSE_DOCKER_IMAGE")
  477. ## SSH config
  478. docker_run_opts+=(
  479. "-v" "$HOME/.ssh:/root/.ssh:ro"
  480. "-v" "/etc/ssh:/etc/ssh:ro"
  481. )
  482. COMPOSE_LAUNCHER_BIN=$(readlink -f "${BASH_SOURCE[0]}")
  483. docker_run_opts+=("-v" "$COMPOSE_LAUNCHER_BIN:/usr/local/bin/compose")
  484. while read-0 var; do
  485. case "$var" in
  486. COMPOSE_YML_FILE|COMPOSE_LAUNCHER_BIN|COMPOSE_DOCKER_IMAGE|\
  487. COMPOSE_LAUNCHER_OPTS|COMPOSE_VAR|COMPOSE_CACHE)
  488. :
  489. ;;
  490. *)
  491. docker_run_opts+=("-e" "$var=${!var}")
  492. ;;
  493. esac
  494. done < <(list_compose_vars)
  495. filename=$(mktemp -p /tmp/ -t launch_opts-XXXXXXXXXXXXXXXX)
  496. {
  497. p0 "${docker_run_opts[@]}"
  498. } > "$filename"
  499. sha=$(sha256sum "$filename")
  500. sha=${sha:0:64}
  501. src="$SESSION_DIR/$UID-$sha"
  502. dest="/var/lib/compose/sessions/$UID-$sha"
  503. {
  504. p0 "-v" "$SESSION_DIR/$UID-$sha:$dest:ro"
  505. p0 "-e" "COMPOSE_LAUNCHER_OPTS=$dest"
  506. p0 "-e" "COMPOSE_LAUNCHER_BIN=$COMPOSE_LAUNCHER_BIN"
  507. } >> "$filename"
  508. mkdir -p "$SESSION_DIR" || return 1
  509. mv -f "$filename" "$SESSION_DIR/$UID-$sha" || return 1
  510. echo "$SESSION_DIR/$UID-$sha"
  511. if [ -n "$DEBUG" ]; then
  512. echo "${WHITE}Environment:${NORMAL}"
  513. echo " COMPOSE_DOCKER_IMAGE: $COMPOSE_DOCKER_IMAGE"
  514. echo " CHARM_STORE: $CHARM_STORE"
  515. echo " DATASTORE: $DATASTORE"
  516. echo " CONFIGSTORE: $CONFIGSTORE"
  517. echo " COMPOSE_VAR: $COMPOSE_VAR"
  518. echo " COMPOSE_CACHE: $COMPOSE_CACHE"
  519. echo " SESSION_DIR: $SESSION_DIR"
  520. echo " TZ_PATH: $TZ_PATH"
  521. fi >&2
  522. }
  523. run() {
  524. local os docker_run_opts
  525. docker_run_opts=()
  526. if [ -z "$COMPOSE_LAUNCHER_OPTS" ]; then
  527. clean_unused_sessions
  528. COMPOSE_LAUNCHER_OPTS="$(mk_docker_run_options)"
  529. fi
  530. while read-0 opt; do
  531. docker_run_opts+=("$opt")
  532. ## catch COMPOSE_DOCKER_IMAGE
  533. if [[ "$env" == "true" && "$opt" == "COMPOSE_DOCKER_IMAGE="* ]]; then
  534. COMPOSE_DOCKER_IMAGE=${opt##COMPOSE_DOCKER_IMAGE=}
  535. elif [ "$opt" == "-e" ]; then
  536. env=true
  537. else
  538. env=
  539. fi
  540. done < <(cat "$COMPOSE_LAUNCHER_OPTS")
  541. [ -t 0 ] && docker_run_opts+=("-i")
  542. [ -t 1 ] && docker_run_opts+=("-t")
  543. if [ -n "$DEBUG" ] || [ -n "$DRY_RUN" ]; then
  544. debug "${WHITE}Launching:${NORMAL}"
  545. echo "docker run --rm \\"
  546. pretty_print "${docker_run_opts[@]}" | sed -r 's/^/ /g;s/([^\])$/\1\\\n/g'
  547. echo " ${COMPOSE_DOCKER_IMAGE} \\"
  548. echo " " "$@"
  549. fi | { if [ -n "$DEBUG" ]; then sed -r 's/^/ /g'; else cat; fi } >&2
  550. if [ -z "$DRY_RUN" ]; then
  551. debug "${WHITE}Execution:${NORMAL}"
  552. exec docker run --rm "${docker_run_opts[@]}" "${COMPOSE_DOCKER_IMAGE}" "$@"
  553. fi
  554. }
  555. [ "$SOURCED" ] && return 0
  556. ##
  557. ## Code
  558. ##
  559. depends docker cat readlink sed realpath
  560. ansi_color "${ansi_color:-tty}"
  561. run "$@"