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.

569 lines
14 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. ANSI_ESC=$'\e['
  11. ansi_color() {
  12. local choice="$1"
  13. if [ "$choice" == "tty" ]; then
  14. if [ -t 1 ]; then
  15. choice="yes"
  16. else
  17. choice="no"
  18. fi
  19. fi
  20. if [ "$choice" != "no" ]; then
  21. SET_COL_CHAR="${ANSI_ESC}${COL_CHAR}G"
  22. SET_COL_STATUS="${ANSI_ESC}${COL_STATUS}G"
  23. SET_COL_INFO="${ANSI_ESC}${COL_INFO}G"
  24. SET_COL_ELT="${ANSI_ESC}${COL_ELT}G"
  25. SET_BEGINCOL="${ANSI_ESC}0G"
  26. UP="${ANSI_ESC}1A"
  27. DOWN="${ANSI_ESC}1B"
  28. LEFT="${ANSI_ESC}1D"
  29. RIGHT="${ANSI_ESC}1C"
  30. SAVE="${ANSI_ESC}7"
  31. RESTORE="${ANSI_ESC}8"
  32. NORMAL="${ANSI_ESC}0m"
  33. GRAY="${ANSI_ESC}1;30m"
  34. RED="${ANSI_ESC}1;31m"
  35. GREEN="${ANSI_ESC}1;32m"
  36. YELLOW="${ANSI_ESC}1;33m"
  37. BLUE="${ANSI_ESC}1;34m"
  38. PINK="${ANSI_ESC}1;35m"
  39. CYAN="${ANSI_ESC}1;36m"
  40. WHITE="${ANSI_ESC}1;37m"
  41. DARKGRAY="${ANSI_ESC}0;30m"
  42. DARKRED="${ANSI_ESC}0;31m"
  43. DARKGREEN="${ANSI_ESC}0;32m"
  44. DARKYELLOW="${ANSI_ESC}0;33m"
  45. DARKBLUE="${ANSI_ESC}0;34m"
  46. DARKPINK="${ANSI_ESC}0;35m"
  47. DARKCYAN="${ANSI_ESC}0;36m"
  48. DARKWHITE="${ANSI_ESC}0;37m"
  49. SUCCESS=$GREEN
  50. WARNING=$YELLOW
  51. FAILURE=$RED
  52. NOOP=$BLUE
  53. ON=$SUCCESS
  54. OFF=$FAILURE
  55. ERROR=$FAILURE
  56. else
  57. SET_COL_CHAR=
  58. SET_COL_STATUS=
  59. SET_COL_INFO=
  60. SET_COL_ELT=
  61. SET_BEGINCOL=
  62. NORMAL=
  63. RED=
  64. GREEN=
  65. YELLOW=
  66. BLUE=
  67. GRAY=
  68. WHITE=
  69. DARKGRAY=
  70. DARKRED=
  71. DARKGREEN=
  72. DARKYELLOW=
  73. DARKBLUE=
  74. DARKPINK=
  75. DARKCYAN=
  76. SUCCESS=
  77. WARNING=
  78. FAILURE=
  79. NOOP=
  80. ON=
  81. OFF=
  82. ERROR=
  83. fi
  84. ansi_color="$choice"
  85. export SET_COL_CHAR SET_COL_STATUS SET_COL_INFO SET_COL_ELT \
  86. SET_BEGINCOL UP DOWN LEFT RIGHT SAVE RESTORE NORMAL \
  87. GRAY RED GREEN YELLOW BLUE PINK CYAN WHITE DARKGRAY \
  88. DARKRED DARKGREEN DARKYELLOW DARKBLUE DARKPINK DARKCYAN \
  89. SUCCESS WARNING FAILURE NOOP ON OFF ERROR ansi_color
  90. }
  91. e() { printf "%s" "$*"; }
  92. warn() { e "${YELLOW}Warning:$NORMAL" "$*"$'\n' >&2 ; }
  93. info() { e "${BLUE}II$NORMAL" "$*"$'\n' >&2 ; }
  94. verb() { [ -z "$VERBOSE" ] || e "$*"$'\n' >&2; }
  95. debug() { [ -z "$DEBUG" ] || e "$*"$'\n' >&2; }
  96. err() { e "${RED}Error:$NORMAL $*"$'\n' >&2 ; }
  97. die() { err "$@" ; exit 1; }
  98. ansi_color "${ansi_color:-tty}"
  99. get_path() { (
  100. IFS=:
  101. for d in $PATH; do
  102. filename="$d/$1"
  103. [ -f "$filename" -a -x "$filename" ] && {
  104. echo "$d/$1"
  105. return 0
  106. }
  107. done
  108. return 1
  109. ) }
  110. depends() {
  111. ## Avoid colliding with variables that are created with depends.
  112. local __i __path __new_name
  113. for __i in "$@"; do
  114. if ! __path=$(get_path "$__i"); then
  115. __new_name="$(echo "${__i//-/_}")"
  116. if [ "$__new_name" != "$__i" ]; then
  117. depends "$__new_name"
  118. else
  119. err "dependency check: couldn't find '$__i' required command." >&2
  120. exit 1
  121. fi
  122. else
  123. if ! test -z "$__path" ; then
  124. export "$(echo "${__i//- /__}")"="$__path"
  125. fi
  126. fi
  127. done
  128. }
  129. get_os() {
  130. local uname_output
  131. uname_output="$(uname -s)"
  132. case "${uname_output}" in
  133. Linux*) e linux;;
  134. Darwin*) e mac;;
  135. CYGWIN*) e cygwin;;
  136. MINGW*) e mingw;;
  137. *) e "UNKNOWN:${uname_output}";;
  138. esac
  139. }
  140. read-0() {
  141. local eof= IFS=''
  142. while [ "$1" ]; do
  143. read -r -d '' -- "$1" || eof=1
  144. shift
  145. done
  146. [ -z "$eof" ]
  147. }
  148. read-0a() {
  149. local eof= IFS=''
  150. while [ "$1" ]; do
  151. IFS='' read -r -d $'\n' -- "$1" || eof=1
  152. shift
  153. done
  154. [ -z "$eof" ]
  155. }
  156. p0() {
  157. printf "%s\0" "$@"
  158. }
  159. list_compose_vars() {
  160. while read-0a def; do
  161. def="${def##* }"
  162. def="${def%=*}"
  163. p0 "$def"
  164. done < <(declare -p | grep "^declare -x COMPOSE_[A-Z_]\+=\"")
  165. }
  166. get_running_compose_containers() {
  167. ## XXXvlab: docker bug: there will be a final newline anyway
  168. docker ps --filter label="compose.service" --format='{{.ID}}'
  169. }
  170. get_volumes_for_container() {
  171. local container="$1"
  172. docker inspect \
  173. --format '{{range $mount := .Mounts}}{{$mount.Source}}{{"\x00"}}{{$mount.Destination}}{{"\x00"}}{{end}}' \
  174. "$container"
  175. }
  176. is_volume_used() {
  177. local volume="$1"
  178. while read container_id; do
  179. while read-0 src dst; do
  180. [ "$src" == "$volume" ] && return 0
  181. done < <(get_volumes_for_container "$container_id")
  182. done < <(get_running_compose_containers)
  183. return 1
  184. }
  185. clean_unused_sessions() {
  186. for f in "$COMPOSE_VAR/sessions/"*; do
  187. [ -e "$f" ] || continue
  188. is_volume_used "$f" && continue
  189. rm -f "$f"
  190. done
  191. }
  192. relink_subdirs() {
  193. local dir
  194. for dir in "$@"; do
  195. [ -L "$dir" ] || continue
  196. target=$(realpath "$dir")
  197. [ -d "$target" ] || continue
  198. docker_run_opts+=("-v" "$target:$dir")
  199. [ -e "$dir/metadata.yml" ] && continue
  200. relink_subdirs "$dir"/*
  201. done
  202. }
  203. mk_docker_run_options() {
  204. ## Order matters, files get to override vars
  205. compose_config_files=(
  206. ## DEFAULT LINUX VARS
  207. /etc/default/charm
  208. /etc/default/datastore
  209. /etc/default/compose
  210. ## COMPOSE SYSTEM-WIDE FILES
  211. /etc/compose.conf
  212. /etc/compose.local.conf
  213. /etc/compose/local.conf
  214. ## COMPOSE USER FILES
  215. ~/.compose/etc/local.conf
  216. ~/.compose.conf
  217. )
  218. docker_run_opts=("-v" "/var/run/docker.sock:/var/run/docker.sock")
  219. ## current dir
  220. if parent=$(while true; do
  221. [ -e "./compose.yml" ] && {
  222. echo "$PWD"
  223. exit 0
  224. }
  225. [ "$PWD" == "/" ] && exit 1
  226. cd ..
  227. done
  228. ); then
  229. docker_path=$COMPOSE_VAR/root/$(basename "$parent")
  230. docker_run_opts+=("-v" "$parent:$docker_path:ro" \
  231. "-w" "$docker_path")
  232. fi
  233. ##
  234. ## Load config files
  235. ##
  236. if [ -z "$DISABLE_SYSTEM_CONFIG_FILE" ]; then
  237. ## XXXvlab: should provide YML config opportunities in possible parent dirs ?
  238. ## userdir ? and global /etc/compose.yml ?
  239. for cfgfile in "${compose_config_files[@]}"; do
  240. [ -e "$cfgfile" ] || continue
  241. docker_run_opts+=("-v" "$cfgfile:$cfgfile:ro")
  242. . "$cfgfile"
  243. done
  244. else
  245. docker_run_opts+=("-e" "DISABLE_SYSTEM_CONFIG_FILE=$DISABLE_SYSTEM_CONFIG_FILE")
  246. fi
  247. COMPOSE_LOCAL_ROOT=${COMPOSE_LOCAL_ROOT:-"$HOME/.compose"}
  248. case "$(get_os)" in
  249. linux)
  250. if [ "$UID" == 0 ]; then
  251. COMPOSE_VAR=${COMPOSE_VAR:-/var/lib/compose}
  252. COMPOSE_CACHE=${COMPOSE_CACHE:-/var/cache/compose}
  253. else
  254. COMPOSE_VAR="$COMPOSE_LOCAL_ROOT"/lib
  255. COMPOSE_CACHE="$COMPOSE_LOCAL_ROOT"/cache
  256. fi
  257. ;;
  258. mac)
  259. COMPOSE_VAR=${COMPOSE_VAR:-"$COMPOSE_LOCAL_ROOT"/lib}
  260. COMPOSE_CACHE=${COMPOSE_CACHE:-"$COMPOSE_LOCAL_ROOT"/cache}
  261. ;;
  262. *)
  263. echo "System '$os' not supported yet." >&2
  264. exit 1
  265. ;;
  266. esac
  267. ## get TZ value and prepare TZ_PATH
  268. TZ=$(get_tz) || exit 1
  269. mkdir -p "${COMPOSE_VAR}/timezones"
  270. TZ_PATH="${COMPOSE_VAR}/timezones/$(e "$TZ" | sha256sum | cut -c 1-8)" || exit 1
  271. [ -e "$TZ_PATH" ] || e "$TZ" > "$TZ_PATH"
  272. ## CACHE/DATA DIRS
  273. docker_run_opts+=("-v" "$COMPOSE_VAR:/var/lib/compose")
  274. docker_run_opts+=("-v" "$COMPOSE_CACHE:/var/cache/compose")
  275. docker_run_opts+=("-v" "$TZ_PATH:/etc/timezone:ro")
  276. ##
  277. ## Checking vars
  278. ##
  279. ## CHARM_STORE
  280. if [ "$UID" == 0 ]; then
  281. CHARM_STORE=${CHARM_STORE:-/srv/charm-store}
  282. else
  283. CHARM_STORE=${CHARM_STORE:-$HOME/.charm-store}
  284. fi
  285. [ -e "$CHARM_STORE" ] || mkdir -p "$CHARM_STORE" || exit 1
  286. [ -L "$CHARM_STORE" ] && {
  287. CHARM_STORE=$(readlink -f "$CHARM_STORE") || exit 1
  288. }
  289. docker_run_opts+=(
  290. "-v" "$CHARM_STORE:/srv/charm-store:ro"
  291. "-e" "CHARM_STORE=/srv/charm-store"
  292. "-e" "HOST_CHARM_STORE=$CHARM_STORE"
  293. )
  294. relink_subdirs "$CHARM_STORE"/*
  295. ## DEFAULT_COMPOSE_FILE
  296. if [ "${DEFAULT_COMPOSE_FILE+x}" ]; then
  297. DEFAULT_COMPOSE_FILE=$(realpath "$DEFAULT_COMPOSE_FILE")
  298. dirname=$(dirname "$DEFAULT_COMPOSE_FILE")/
  299. if [ -e "${DEFAULT_COMPOSE_FILE}" ]; then
  300. docker_run_opts+=("-v" "$dirname:$dirname:ro")
  301. fi
  302. fi
  303. ## COMPOSE_YML_FILE
  304. if [ "${COMPOSE_YML_FILE+x}" ]; then
  305. if [ -e "${COMPOSE_YML_FILE}" ]; then
  306. docker_run_opts+=(
  307. "-v" "$COMPOSE_YML_FILE:/tmp/compose.yml:ro"
  308. "-e" "COMPOSE_YML_FILE=/tmp/compose.yml"
  309. "-e" "HOST_COMPOSE_YML_FILE=/tmp/compose.yml"
  310. )
  311. fi
  312. fi
  313. ## DATASTORE
  314. if [ "$UID" == 0 ]; then
  315. DATASTORE=${DATASTORE:-/srv/datastore/data}
  316. else
  317. DATASTORE=${DATASTORE:-"$COMPOSE_LOCAL_ROOT"/data}
  318. fi
  319. docker_run_opts+=(
  320. "-v" "$DATASTORE:/srv/datastore/data:rw"
  321. "-e" "DATASTORE=/srv/datastore/data"
  322. "-e" "HOST_DATASTORE=$DATASTORE"
  323. )
  324. ## CONFIGSTORE
  325. if [ "$UID" == 0 ]; then
  326. CONFIGSTORE=${CONFIGSTORE:-/srv/datastore/config}
  327. else
  328. CONFIGSTORE=${CONFIGSTORE:-"$COMPOSE_LOCAL_ROOT"/data}
  329. fi
  330. docker_run_opts+=(
  331. "-v" "$CONFIGSTORE:/srv/datastore/config:rw"
  332. "-e" "CONFIGSTORE=/srv/datastore/config"
  333. "-e" "HOST_CONFIGSTORE=$CONFIGSTORE"
  334. )
  335. docker_run_opts+=("-v" "$HOME/.docker:/root/.docker")
  336. COMPOSE_DOCKER_IMAGE=${COMPOSE_DOCKER_IMAGE:-docker.0k.io/compose}
  337. docker_run_opts+=("-e" "COMPOSE_DOCKER_IMAGE=$COMPOSE_DOCKER_IMAGE")
  338. ## SSH config
  339. docker_run_opts+=(
  340. "-v" "$HOME/.ssh:/root/.ssh:ro"
  341. "-v" "/etc/ssh:/etc/ssh:ro"
  342. )
  343. COMPOSE_LAUNCHER_BIN=$(readlink -f "${BASH_SOURCE[0]}")
  344. docker_run_opts+=("-v" "$COMPOSE_LAUNCHER_BIN:/usr/local/bin/compose")
  345. while read-0 var; do
  346. case "$var" in
  347. COMPOSE_YML_FILE|COMPOSE_LAUNCHER_BIN|COMPOSE_DOCKER_IMAGE|\
  348. COMPOSE_LAUNCHER_OPTS|COMPOSE_VAR|COMPOSE_CACHE)
  349. :
  350. ;;
  351. *)
  352. docker_run_opts+=("-e" "$var=${!var}")
  353. ;;
  354. esac
  355. done < <(list_compose_vars)
  356. filename=$(mktemp -p /tmp/ -t launch_opts-XXXXXXXXXXXXXXXX)
  357. {
  358. p0 "${docker_run_opts[@]}"
  359. } > "$filename"
  360. sha=$(sha256sum "$filename")
  361. sha=${sha:0:64}
  362. dest="$COMPOSE_VAR/sessions/$sha"
  363. {
  364. p0 "-v" "$dest:$dest"
  365. p0 "-e" "COMPOSE_LAUNCHER_OPTS=$dest"
  366. p0 "-e" "COMPOSE_LAUNCHER_BIN=$COMPOSE_LAUNCHER_BIN"
  367. } >> "$filename"
  368. mkdir -p "$COMPOSE_VAR"/sessions
  369. mv "$filename" "$dest"
  370. echo "$dest"
  371. if [ "$DEBUG" ]; then
  372. echo "${WHITE}Environment:${NORMAL}"
  373. echo " COMPOSE_DOCKER_IMAGE: $COMPOSE_DOCKER_IMAGE"
  374. echo " CHARM_STORE: $CHARM_STORE"
  375. echo " DATASTORE: $DATASTORE"
  376. echo " CONFIGSTORE: $CONFIGSTORE"
  377. echo " COMPOSE_VAR: $COMPOSE_VAR"
  378. echo " COMPOSE_CACHE: $COMPOSE_CACHE"
  379. fi >&2
  380. }
  381. get_tz() {
  382. if [ -n "$TZ" ]; then
  383. :
  384. elif [ -n "$COMPOSE_LOCAL_ROOT" ] && ## previous compose run
  385. [ -e "$COMPOSE_LOCAL_ROOT/etc/timezone" ]; then
  386. read -r TZ < /etc/timezone
  387. elif [ -e "/etc/timezone" ]; then ## debian host system timezone
  388. read -r TZ < /etc/timezone
  389. elif [ -e "/etc/localtime" ]; then ## redhat and macosx sys timezone
  390. local fullpath dirname
  391. fullpath="$(readlink -f /etc/localtime)"
  392. dirname="${fullpath%/*}"
  393. TZ=${TZ:-${fullpath##*/}/${dirname##*/}}
  394. else
  395. err "Timezone not found nor inferable !"
  396. echo " compose relies on '/etc/timezone' or '/etc/localtime' to be present " >&2
  397. echo " so as to ensure same timezone for all containers that need it." >&2
  398. echo >&2
  399. echo " You can set a default value for compose by create issuing:" >&2
  400. echo >&2
  401. if [ -n "$COMPOSE_LOCAL_ROOT" ] && [ "$UID" != 0 ]; then
  402. echo " mkdir -p $COMPOSE_LOCAL_ROOT/etc &&" >&2
  403. echo " echo \"Europe/Paris\" > $COMPOSE_LOCAL_ROOT/etc/timezone" >&2
  404. else
  405. echo " echo \"Europe/Paris\" > /etc/timezone" >&2
  406. fi
  407. echo >&2
  408. echo " Of course, you can change 'Europe/Paris' value by any other valid timezone." >&2
  409. echo " timezone." >&2
  410. echo >&2
  411. echo " Notice you can also use \$TZ environment variable, but the value" >&2
  412. echo " will be necessary each time you launch compose." >&2
  413. echo >&2
  414. return 1
  415. fi
  416. e "$TZ"
  417. }
  418. pretty_print() {
  419. while [ "$#" != 0 ]; do
  420. case "$1" in
  421. "-v"|"-e"|"-w")
  422. e "$1" "$2" "\\"$'\n'
  423. shift;;
  424. *)
  425. e "$1 ";;
  426. esac
  427. shift
  428. done
  429. }
  430. run() {
  431. local os docker_run_opts
  432. docker_run_opts=()
  433. if [ -z "$COMPOSE_LAUNCHER_OPTS" ]; then
  434. clean_unused_sessions
  435. COMPOSE_LAUNCHER_OPTS="$(mk_docker_run_options)"
  436. fi
  437. while read-0 opt; do
  438. docker_run_opts+=("$opt")
  439. ## catch COMPOSE_DOCKER_IMAGE
  440. if [[ "$env" == "true" && "$opt" == "COMPOSE_DOCKER_IMAGE="* ]]; then
  441. COMPOSE_DOCKER_IMAGE=${opt##COMPOSE_DOCKER_IMAGE=}
  442. elif [ "$opt" == "-e" ]; then
  443. env=true
  444. else
  445. env=
  446. fi
  447. done < <(cat "$COMPOSE_LAUNCHER_OPTS")
  448. [ -t 0 ] && docker_run_opts+=("-i")
  449. [ -t 1 ] && docker_run_opts+=("-t")
  450. if [ "$DEBUG" ]; then
  451. echo "${WHITE}Launching:${NORMAL}"
  452. echo " docker run --rm \\"
  453. pretty_print "${docker_run_opts[@]}" | sed -r 's/^/ /g' >&2
  454. echo "\\"$'\n' >&2
  455. echo " ${COMPOSE_DOCKER_IMAGE} \\"
  456. echo " " "$@"
  457. fi >&2
  458. exec docker run --rm "${docker_run_opts[@]}" "${COMPOSE_DOCKER_IMAGE}" "$@"
  459. }
  460. [ "$SOURCED" ] && return 0
  461. ##
  462. ## Code
  463. ##
  464. depends readlink
  465. run "$@"