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.

522 lines
13 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."
  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_VAR:-"$COMPOSE_LOCAL_ROOT"/lib}
  255. COMPOSE_CACHE=${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. CHARM_STORE=${CHARM_STORE:-/srv/charm-store}
  281. [ -L "$CHARM_STORE" ] && {
  282. CHARM_STORE=$(readlink -f "$CHARM_STORE") || exit 1
  283. }
  284. docker_run_opts+=(
  285. "-v" "$CHARM_STORE:/srv/charm-store:ro"
  286. "-e" "CHARM_STORE=/srv/charm-store"
  287. "-e" "HOST_CHARM_STORE=$CHARM_STORE"
  288. )
  289. relink_subdirs /srv/charm-store/*
  290. ## DEFAULT_COMPOSE_FILE
  291. if [ "${DEFAULT_COMPOSE_FILE+x}" ]; then
  292. DEFAULT_COMPOSE_FILE=$(realpath "$DEFAULT_COMPOSE_FILE")
  293. dirname=$(dirname "$DEFAULT_COMPOSE_FILE")/
  294. if [ -e "${DEFAULT_COMPOSE_FILE}" ]; then
  295. docker_run_opts+=("-v" "$dirname:$dirname:ro")
  296. fi
  297. fi
  298. ## COMPOSE_YML_FILE
  299. if [ "${COMPOSE_YML_FILE+x}" ]; then
  300. if [ -e "${COMPOSE_YML_FILE}" ]; then
  301. docker_run_opts+=(
  302. "-v" "$COMPOSE_YML_FILE:/tmp/compose.yml:ro"
  303. "-e" "COMPOSE_YML_FILE=/tmp/compose.yml"
  304. "-e" "HOST_COMPOSE_YML_FILE=/tmp/compose.yml"
  305. )
  306. fi
  307. fi
  308. ## DATASTORE
  309. DATASTORE=${DATASTORE:-/srv/datastore/data}
  310. docker_run_opts+=(
  311. "-v" "$DATASTORE:/srv/datastore/data:rw"
  312. "-e" "DATASTORE=/srv/datastore/data"
  313. "-e" "HOST_DATASTORE=$DATASTORE"
  314. )
  315. ## CONFIGSTORE
  316. CONFIGSTORE=${CONFIGSTORE:-/srv/datastore/config}
  317. docker_run_opts+=(
  318. "-v" "$CONFIGSTORE:/srv/datastore/config:rw"
  319. "-e" "CONFIGSTORE=/srv/datastore/config"
  320. "-e" "HOST_CONFIGSTORE=$CONFIGSTORE"
  321. )
  322. docker_run_opts+=("-v" "$HOME/.docker:/root/.docker")
  323. COMPOSE_DOCKER_IMAGE=${COMPOSE_DOCKER_IMAGE:-docker.0k.io/compose}
  324. docker_run_opts+=("-e" "COMPOSE_DOCKER_IMAGE=$COMPOSE_DOCKER_IMAGE")
  325. ## SSH config
  326. docker_run_opts+=(
  327. "-v" "/root/.ssh:/root/.ssh:ro"
  328. "-v" "/etc/ssh:/etc/ssh"
  329. )
  330. COMPOSE_LAUNCHER_BIN=$(readlink -f "${BASH_SOURCE[0]}")
  331. docker_run_opts+=("-v" "$COMPOSE_LAUNCHER_BIN:/usr/local/bin/compose")
  332. while read-0 var; do
  333. case "$var" in
  334. COMPOSE_YML_FILE|COMPOSE_LAUNCHER_BIN|COMPOSE_DOCKER_IMAGE|\
  335. COMPOSE_LAUNCHER_OPTS|COMPOSE_VAR|COMPOSE_CACHE)
  336. :
  337. ;;
  338. *)
  339. docker_run_opts+=("-e" "$var=${!var}")
  340. ;;
  341. esac
  342. done < <(list_compose_vars)
  343. filename=$(mktemp -p /tmp/ -t launch_opts-XXXXXXXXXXXXXXXX)
  344. {
  345. p0 "${docker_run_opts[@]}"
  346. } > "$filename"
  347. sha=$(sha256sum "$filename")
  348. sha=${sha:0:64}
  349. dest="$COMPOSE_VAR/sessions/$sha"
  350. {
  351. p0 "-v" "$dest:$dest"
  352. p0 "-e" "COMPOSE_LAUNCHER_OPTS=$dest"
  353. p0 "-e" "COMPOSE_LAUNCHER_BIN=$COMPOSE_LAUNCHER_BIN"
  354. } >> "$filename"
  355. mkdir -p "$COMPOSE_VAR"/sessions
  356. mv "$filename" "$dest"
  357. echo "$dest"
  358. }
  359. get_tz() {
  360. if [ -n "$TZ" ]; then
  361. :
  362. elif [ -n "$COMPOSE_LOCAL_ROOT" ] && ## previous compose run
  363. [ -e "$COMPOSE_LOCAL_ROOT/etc/timezone" ]; then
  364. read -r TZ < "$COMPOSE_LOCAL_ROOT/etc/timezone"
  365. elif [ -e "/etc/timezone" ]; then ## debian host system timezone
  366. read -r TZ < /etc/timezone
  367. elif [ -e "/etc/localtime" ]; then ## redhat and macosx sys timezone
  368. local fullpath dirname
  369. fullpath="$(readlink -f /etc/localtime)"
  370. dirname="${fullpath%/*}"
  371. TZ=${TZ:-${fullpath##*/}/${dirname##*/}}
  372. else
  373. err "Timezone not found nor inferable !"
  374. echo " compose relies on '/etc/timezone' or '/etc/localtime' to be present " >&2
  375. echo " so as to ensure same timezone for all containers that need it." >&2
  376. echo >&2
  377. echo " You can set a default value for compose by create issuing:" >&2
  378. echo >&2
  379. if [ -n "$COMPOSE_LOCAL_ROOT" ] && [ "$UID" != 0 ]; then
  380. echo " mkdir -p $COMPOSE_LOCAL_ROOT/etc &&" >&2
  381. echo " echo \"Europe/Paris\" > $COMPOSE_LOCAL_ROOT/etc/timezone" >&2
  382. else
  383. echo " echo \"Europe/Paris\" > /etc/timezone" >&2
  384. fi
  385. echo >&2
  386. echo " Of course, you can change 'Europe/Paris' value by any other valid timezone." >&2
  387. echo " timezone." >&2
  388. echo >&2
  389. echo " Notice you can also use \$TZ environment variable, but the value" >&2
  390. echo " will be necessary each time you launch compose." >&2
  391. echo >&2
  392. return 1
  393. fi
  394. e "$TZ"
  395. }
  396. run() {
  397. local os docker_run_opts
  398. docker_run_opts=()
  399. if [ -z "$COMPOSE_LAUNCHER_OPTS" ]; then
  400. clean_unused_sessions
  401. COMPOSE_LAUNCHER_OPTS="$(mk_docker_run_options)"
  402. fi
  403. while read-0 opt; do
  404. docker_run_opts+=("$opt")
  405. ## catch COMPOSE_DOCKER_IMAGE
  406. if [[ "$env" == "true" && "$opt" == "COMPOSE_DOCKER_IMAGE="* ]]; then
  407. COMPOSE_DOCKER_IMAGE=${opt##COMPOSE_DOCKER_IMAGE=}
  408. elif [ "$opt" == "-e" ]; then
  409. env=true
  410. else
  411. env=
  412. fi
  413. done < <(cat "$COMPOSE_LAUNCHER_OPTS")
  414. [ -t 0 ] && docker_run_opts+=("-i")
  415. [ -t 1 ] && docker_run_opts+=("-t")
  416. exec docker run --rm "${docker_run_opts[@]}" "${COMPOSE_DOCKER_IMAGE}" "$@"
  417. }
  418. [ "$SOURCED" ] && return 0
  419. ##
  420. ## Code
  421. ##
  422. depends readlink
  423. run "$@"