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
15 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 "$SESSION_DIR/"*; 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. COMPOSE_VAR=${COMPOSE_VAR:-/var/lib/compose}
  251. COMPOSE_CACHE=${COMPOSE_CACHE:-/var/cache/compose}
  252. DATASTORE=${DATASTORE:-/srv/datastore/data}
  253. CONFIGSTORE=${CONFIGSTORE:-/srv/datastore/config}
  254. if [ "$UID" == 0 ]; then
  255. SESSION_DIR=${SESSION_DIR:-"$COMPOSE_VAR"/sessions}
  256. CHARM_STORE=${CHARM_STORE:-/srv/charm-store}
  257. TZ_PATH=${TZ_PATH:-"$COMPOSE_VAR"/timezones}
  258. else
  259. SESSION_DIR=${SESSION_DIR:-"$COMPOSE_LOCAL_ROOT"/sessions}
  260. CHARM_STORE=${CHARM_STORE:-"$HOME"/.charm-store}
  261. TZ_PATH=${TZ_PATH:-"$COMPOSE_LOCAL_ROOT"/timezones}
  262. fi
  263. ;;
  264. mac)
  265. COMPOSE_VAR=${COMPOSE_VAR:-"$COMPOSE_LOCAL_ROOT"/lib}
  266. COMPOSE_CACHE=${COMPOSE_CACHE:-"$COMPOSE_LOCAL_ROOT"/cache}
  267. SESSION_DIR=${SESSION_DIR:-"$COMPOSE_LOCAL_ROOT"/sessions}
  268. DATASTORE=${DATASTORE:-"$COMPOSE_LOCAL_ROOT"/data}
  269. CONFIGSTORE=${CONFIGSTORE:-"$COMPOSE_LOCAL_ROOT"/config}
  270. CHARM_STORE=${CHARM_STORE:-"$HOME"/.charm-store}
  271. TZ_PATH=${TZ_PATH:-"$COMPOSE_LOCAL_ROOT"/timezones}
  272. ;;
  273. *)
  274. echo "System '$os' not supported yet." >&2
  275. exit 1
  276. ;;
  277. esac
  278. ## get TZ value and prepare TZ_PATH
  279. TZ=$(get_tz) || exit 1
  280. mkdir -p "${TZ_PATH}"
  281. TZ_PATH="${TZ_PATH}/$(e "$TZ" | sha256sum | cut -c 1-8)" || exit 1
  282. [ -e "$TZ_PATH" ] || e "$TZ" > "$TZ_PATH"
  283. ## CACHE/DATA DIRS
  284. docker_run_opts+=("-v" "$COMPOSE_VAR:/var/lib/compose")
  285. docker_run_opts+=("-v" "$COMPOSE_CACHE:/var/cache/compose")
  286. docker_run_opts+=("-v" "$TZ_PATH:/etc/timezone:ro")
  287. ##
  288. ## Checking vars
  289. ##
  290. ## CHARM_STORE
  291. [ -e "$CHARM_STORE" ] || mkdir -p "$CHARM_STORE" || exit 1
  292. [ -L "$CHARM_STORE" ] && {
  293. CHARM_STORE=$(readlink -f "$CHARM_STORE") || exit 1
  294. }
  295. docker_run_opts+=(
  296. "-v" "$CHARM_STORE:/srv/charm-store:ro"
  297. "-e" "CHARM_STORE=/srv/charm-store"
  298. "-e" "HOST_CHARM_STORE=$CHARM_STORE"
  299. )
  300. relink_subdirs "$CHARM_STORE"/*
  301. ## DEFAULT_COMPOSE_FILE
  302. if [ "${DEFAULT_COMPOSE_FILE+x}" ]; then
  303. DEFAULT_COMPOSE_FILE=$(realpath "$DEFAULT_COMPOSE_FILE")
  304. dirname=$(dirname "$DEFAULT_COMPOSE_FILE")/
  305. if [ -e "${DEFAULT_COMPOSE_FILE}" ]; then
  306. docker_run_opts+=("-v" "$dirname:$dirname:ro")
  307. fi
  308. fi
  309. ## COMPOSE_YML_FILE
  310. if [ "${COMPOSE_YML_FILE+x}" ]; then
  311. if [ -e "${COMPOSE_YML_FILE}" ]; then
  312. docker_run_opts+=(
  313. "-v" "$COMPOSE_YML_FILE:/tmp/compose.yml:ro"
  314. "-e" "COMPOSE_YML_FILE=/tmp/compose.yml"
  315. "-e" "HOST_COMPOSE_YML_FILE=/tmp/compose.yml"
  316. )
  317. fi
  318. fi
  319. ## DATASTORE and CONFIGSTORE
  320. docker_run_opts+=(
  321. "-v" "$DATASTORE:/srv/datastore/data:rw"
  322. "-e" "DATASTORE=/srv/datastore/data"
  323. "-e" "HOST_DATASTORE=$DATASTORE"
  324. "-v" "$CONFIGSTORE:/srv/datastore/config:rw"
  325. "-e" "CONFIGSTORE=/srv/datastore/config"
  326. "-e" "HOST_CONFIGSTORE=$CONFIGSTORE"
  327. )
  328. docker_run_opts+=("-v" "$HOME/.docker:/root/.docker")
  329. COMPOSE_DOCKER_IMAGE=${COMPOSE_DOCKER_IMAGE:-docker.0k.io/compose}
  330. docker_run_opts+=("-e" "COMPOSE_DOCKER_IMAGE=$COMPOSE_DOCKER_IMAGE")
  331. ## SSH config
  332. docker_run_opts+=(
  333. "-v" "$HOME/.ssh:/root/.ssh:ro"
  334. "-v" "/etc/ssh:/etc/ssh:ro"
  335. )
  336. COMPOSE_LAUNCHER_BIN=$(readlink -f "${BASH_SOURCE[0]}")
  337. docker_run_opts+=("-v" "$COMPOSE_LAUNCHER_BIN:/usr/local/bin/compose")
  338. while read-0 var; do
  339. case "$var" in
  340. COMPOSE_YML_FILE|COMPOSE_LAUNCHER_BIN|COMPOSE_DOCKER_IMAGE|\
  341. COMPOSE_LAUNCHER_OPTS|COMPOSE_VAR|COMPOSE_CACHE)
  342. :
  343. ;;
  344. *)
  345. docker_run_opts+=("-e" "$var=${!var}")
  346. ;;
  347. esac
  348. done < <(list_compose_vars)
  349. filename=$(mktemp -p /tmp/ -t launch_opts-XXXXXXXXXXXXXXXX)
  350. {
  351. p0 "${docker_run_opts[@]}"
  352. } > "$filename"
  353. sha=$(sha256sum "$filename")
  354. sha=${sha:0:64}
  355. src="$SESSION_DIR/$UID-$sha"
  356. dest="/var/lib/compose/sessions/$UID-$sha"
  357. {
  358. p0 "-v" "$SESSION_DIR/$UID-$sha:$dest:ro"
  359. p0 "-e" "COMPOSE_LAUNCHER_OPTS=$dest"
  360. p0 "-e" "COMPOSE_LAUNCHER_BIN=$COMPOSE_LAUNCHER_BIN"
  361. } >> "$filename"
  362. mkdir -p "$SESSION_DIR" || return 1
  363. mv -f "$filename" "$SESSION_DIR/$UID-$sha" || return 1
  364. echo "$SESSION_DIR/$UID-$sha"
  365. if [ -n "$DEBUG" ]; then
  366. echo "${WHITE}Environment:${NORMAL}"
  367. echo " COMPOSE_DOCKER_IMAGE: $COMPOSE_DOCKER_IMAGE"
  368. echo " CHARM_STORE: $CHARM_STORE"
  369. echo " DATASTORE: $DATASTORE"
  370. echo " CONFIGSTORE: $CONFIGSTORE"
  371. echo " COMPOSE_VAR: $COMPOSE_VAR"
  372. echo " COMPOSE_CACHE: $COMPOSE_CACHE"
  373. echo " SESSION_DIR: $SESSION_DIR"
  374. echo " TZ_PATH: $TZ_PATH"
  375. fi >&2
  376. }
  377. get_tz() {
  378. if [ -n "$TZ" ]; then
  379. :
  380. elif [ -n "$COMPOSE_LOCAL_ROOT" ] && ## previous compose run
  381. [ -e "$COMPOSE_LOCAL_ROOT/etc/timezone" ]; then
  382. read -r TZ < "$COMPOSE_LOCAL_ROOT/etc/timezone"
  383. elif [ -e "/etc/timezone" ]; then ## debian host system timezone
  384. read -r TZ < /etc/timezone
  385. elif [ -e "/etc/localtime" ]; then ## redhat and macosx sys timezone
  386. local fullpath dirname
  387. fullpath="$(readlink -f /etc/localtime)"
  388. dirname="${fullpath%/*}"
  389. TZ=${TZ:-${fullpath##*/}/${dirname##*/}}
  390. else
  391. err "Timezone not found nor inferable !"
  392. echo " compose relies on '/etc/timezone' or '/etc/localtime' to be present " >&2
  393. echo " so as to ensure same timezone for all containers that need it." >&2
  394. echo >&2
  395. echo " You can set a default value for compose by create issuing:" >&2
  396. echo >&2
  397. if [ -n "$COMPOSE_LOCAL_ROOT" ] && [ "$UID" != 0 ]; then
  398. echo " mkdir -p $COMPOSE_LOCAL_ROOT/etc &&" >&2
  399. echo " echo \"Europe/Paris\" > $COMPOSE_LOCAL_ROOT/etc/timezone" >&2
  400. else
  401. echo " echo \"Europe/Paris\" > /etc/timezone" >&2
  402. fi
  403. echo >&2
  404. echo " Of course, you can change 'Europe/Paris' value by any other valid timezone." >&2
  405. echo " timezone." >&2
  406. echo >&2
  407. echo " Notice you can also use \$TZ environment variable, but the value" >&2
  408. echo " will be necessary each time you launch compose." >&2
  409. echo >&2
  410. return 1
  411. fi
  412. e "$TZ"
  413. }
  414. pretty_print() {
  415. while [ "$#" != 0 ]; do
  416. case "$1" in
  417. "-v"|"-e"|"-w")
  418. e "$1" "$2" "\\"$'\n'
  419. shift;;
  420. *)
  421. e "$1 ";;
  422. esac
  423. shift
  424. done
  425. }
  426. run() {
  427. local os docker_run_opts
  428. docker_run_opts=()
  429. if [ -z "$COMPOSE_LAUNCHER_OPTS" ]; then
  430. clean_unused_sessions
  431. COMPOSE_LAUNCHER_OPTS="$(mk_docker_run_options)"
  432. fi
  433. while read-0 opt; do
  434. docker_run_opts+=("$opt")
  435. ## catch COMPOSE_DOCKER_IMAGE
  436. if [[ "$env" == "true" && "$opt" == "COMPOSE_DOCKER_IMAGE="* ]]; then
  437. COMPOSE_DOCKER_IMAGE=${opt##COMPOSE_DOCKER_IMAGE=}
  438. elif [ "$opt" == "-e" ]; then
  439. env=true
  440. else
  441. env=
  442. fi
  443. done < <(cat "$COMPOSE_LAUNCHER_OPTS")
  444. [ -t 0 ] && docker_run_opts+=("-i")
  445. [ -t 1 ] && docker_run_opts+=("-t")
  446. if [ -n "$DEBUG" ] || [ -n "$DRY_RUN" ]; then
  447. debug "${WHITE}Launching:${NORMAL}"
  448. echo "docker run --rm \\"
  449. pretty_print "${docker_run_opts[@]}" | sed -r 's/^/ /g;s/([^\])$/\1\\\n/g'
  450. echo " ${COMPOSE_DOCKER_IMAGE} \\"
  451. echo " " "$@"
  452. fi | { if [ -n "$DEBUG" ]; then sed -r 's/^/ /g'; else cat; fi } >&2
  453. if [ -z "$DRY_RUN" ]; then
  454. debug "${WHITE}Execution:${NORMAL}"
  455. exec docker run --rm "${docker_run_opts[@]}" "${COMPOSE_DOCKER_IMAGE}" "$@"
  456. fi
  457. }
  458. [ "$SOURCED" ] && return 0
  459. ##
  460. ## Code
  461. ##
  462. depends readlink
  463. run "$@"