diff --git a/bin/compose b/bin/compose index 48d9c51..1fd2dfd 100755 --- a/bin/compose +++ b/bin/compose @@ -49,6 +49,18 @@ quick_cat_file() { quick_cat_stdin < "$1"; } quick_cat_stdin() { local IFS=''; while read -r line; do echo "$line"; done ; } export -f quick_cat_file quick_cat_stdin md5_compat +clean_cache() { + local i=0 + for f in $(ls -t "$CACHEDIR/"*.cache.* 2>/dev/null | tail -n +500); do + ((i++)) + rm -f "$f" + done + if (( i > 0 )); then + debug "${WHITE}Cleaned cache:${NORMAL} Removed $((i)) elements (current cache size is $(du -sh "$CACHEDIR" | cut -f 1))" + fi +} + + trap_add "EXIT" clean_cache usage="$exname CHARM"' @@ -330,6 +342,219 @@ get_file() { export -f get_file +## +## Common database lib +## + +_clean_docker() { + local _DB_NAME="$1" container_id="$2" + ( + set +e + debug "Removing container $_DB_NAME" + docker stop "$container_id" + docker rm "$_DB_NAME" + rm -vf "/tmp/${_DB_NAME}.state" + ) +} +export -f _clean_docker + + +are_files_locked_in_dir() { + local dir="$1" device hdev ldev + device=$(stat -c %d "$dir") || { + err "Can't stat %d." + return 1 + } + device=$(printf "%04x" $device) + hdev=${device:0:2} + ldev=${device:2:2} + inodes=$(find "$dir" -printf ':%i:\n') + + found= + while read -r inode; do + debug "try inode:$inode" + if [[ "$inodes" == *":$inode:"* ]]; then + found=1 + break + fi + done < <(cat /proc/locks | grep " $hdev:$ldev:" | sed -r "s/^.*$hdev:$ldev:([0-9]+).*$/\1/g") + + [ "$found" ] +} +export -f are_files_locked_in_dir + +export _PID="$$" +ensure_db_docker_running () { + + _DB_NAME="db_${DB_NAME}_${_PID}" + if [ -e "/tmp/${_DB_NAME}.state" ]; then + export DOCKER_IP="$(cat "/tmp/${_DB_NAME}.state")" + debug "Re-using previous docker/connection '$DOCKER_IP'." + _set_db_params "$DOCKER_IP" + return 0 + fi + + if [ -e "/tmp/${_DB_NAME}.working" ]; then + ## avoid recursive calls. + if [ -z "$DOCKER_IP" ]; then + err "Currently figuring up DOCKER_IP, please set it yourself before this call if needed." + return 1 + else + debug "Ignoring recursive call." + fi + return 0 + fi + + touch "/tmp/${_DB_NAME}.working" + + docker rm "$_DB_NAME" 2>/dev/null || true + + host_db_working_dir="$DATASTORE/${SERVICE_NAME}$DB_DATADIR" + + if is_db_locked; then + info "Some process is using '$host_db_working_dir'. Trying to find a docker that would do this..." + found= + for docker_id in $(docker ps -q); do + has_volume_mounted=$( + docker inspect \ + --format "{{range .Mounts}}{{if eq .Destination \"$DB_DATADIR\"}}{{.Source}}{{end}}{{end}}" \ + "$docker_id") + if [ "$has_volume_mounted" == "$host_db_working_dir" ]; then + found="$docker_id" + break + fi + done + if [ -z "$found" ]; then + err "Please shutdown any other docker using this directory." + return 1 + fi + export container_id="$found" + info "Found docker $docker_id is already running." + else + verb "Database is not locked." + if ! docker_has_image "$DOCKER_BASE_IMAGE"; then + docker pull "$DOCKER_BASE_IMAGE" + fi + + docker_opts= + if [ -f "$DB_PASSFILE" ]; then + verb "Found and using '$DB_PASSFILE'." + docker_opts="$db_docker_opts -v $SERVER_ROOT_PREFIX$DB_PASSFILE:$DB_PASSFILE" + fi + debug docker run -d \ + --name "$_DB_NAME" \ + $docker_opts \ + -v "$host_db_working_dir:$DB_DATADIR" \ + "$DOCKER_BASE_IMAGE" + if ! container_id=$( + docker run -d \ + --name "$_DB_NAME" \ + $docker_opts \ + -v "$host_db_working_dir:$DB_DATADIR" \ + "$DOCKER_BASE_IMAGE" + ); then + err "'docker run' failed !" + _clean_docker "$_DB_NAME" "$container_id" + rm "/tmp/${_DB_NAME}.working" + return 1 + fi + trap_add EXIT,ERR "_clean_docker '$_DB_NAME' '$container_id'" + + fi + + + if DOCKER_IP=$(wait_for_docker_ip "$container_id"); then + echo "$DOCKER_IP" > "/tmp/${_DB_NAME}.state" + debug "written /tmp/${_DB_NAME}.state" + rm "/tmp/${_DB_NAME}.working" + _set_db_params "$DOCKER_IP" + return 0 + else + err "Db not found. Docker logs follows:" + docker logs --tail=5 "$container_id" 2>&1 | prefix " | " >&2 + rm "/tmp/${_DB_NAME}.working" + return 1 + fi +} +export -f ensure_db_docker_running + +## Require to set $db_docker_opts if needed, and $DB_PASSFILE +## +_dcmd() { + local docker_opts command="$1" + shift + + debug "Db> $command $@" + + if [ -f "$DB_PASSFILE" ]; then + verb "Found and using '$DB_PASSFILE'." + db_docker_opts="$db_docker_opts -v $SERVER_ROOT_PREFIX$DB_PASSFILE:$DB_PASSFILE" + fi + + debug docker run -i --rm \ + $db_docker_opts \ + --entrypoint "$command" "$DOCKER_BASE_IMAGE" $db_cmd_opts "$@" + docker run -i --rm \ + $db_docker_opts \ + --entrypoint "$command" "$DOCKER_BASE_IMAGE" $db_cmd_opts "$@" +} +export -f _dcmd + + +## Executes code through db +dcmd () { + + [ "$DB_NAME" ] || print_syntax_error "$FUNCNAME: You must provide \$DB_NAME." + [ "$DB_DATADIR" ] || print_syntax_error "$FUNCNAME: You must provide \$DB_DATADIR." + [ "$DB_PASSFILE" ] || print_syntax_error "$FUNCNAME: You must provide \$DB_PASSFILE." + [ "$_PID" ] || print_syntax_error "$FUNCNAME: You must provide \$_PID." + [ "$(type -t is_db_locked)" == "function" ] || print_syntax_error "$FUNCNAME: You must provide function 'is_db_locked'." + [ "$(type -t _set_db_params)" == "function" ] || print_syntax_error "$FUNCNAME: You must provide function '_set_db_params'." + [ "$(type -t ddb)" == "function" ] || print_syntax_error "$FUNCNAME: You must provide function 'ddb'." + + ensure_db_docker_running /dev/null) + verb "[1/2] Waiting for docker $name... ($[timeout_count + 1]/$timeout)" + ((timeout_count++)) || true + if [ "$timeout_count" == "$timeout" ]; then + err "${RED}timeout error${NORMAL}(${timeout}s):"\ + "Could not find '$name' docker" \ + "container's IP." + return 1 + fi + done + verb "[1/2] Found docker $name IP: $DOCKER_IP" + timeout_count=0 + DB_OK= + _set_db_params "$DOCKER_IP" + while [ -z "$DB_OK" ]; do + sleep 1 + echo "SELECT 1;" | ddb >/dev/null && DB_OK=1 + verb "[2/2] Waiting for db service from docker $name... ($[timeout_count + 1]/$timeout)" + ((timeout_count++)) || true + if [ "$timeout_count" == "$timeout" ]; then + err "${RED}timeout error${NORMAL}(${timeout}s):"\ + "Could not connect to db on $DOCKER_IP." \ + "container's IP." + return 1 + fi + done + verb "[2/2] Db is ready !" + echo "$DOCKER_IP" + return 0 +} +export -f wait_for_docker_ip + ## ## Arrays ##