Initial commit

This commit is contained in:
Christian Tosta
2024-03-27 10:15:34 -03:00
commit 125ddd20d5
20 changed files with 1959 additions and 0 deletions

19
lib/builtin/getopts.bash Normal file
View File

@@ -0,0 +1,19 @@
#!/usr/bin/bash
declare -g options=':'
declare -Ag arguments=()
function _requires_opt() {
set +u
local _opt=${1:-}
local _var=$(eval echo ${2:-})
if [[ -z "${_var}" ]] && [[ ! -z "${_opt}" ]]; then
${ui}.error && \
${ui}.print \
$"Required option missing: '-%s'. You must provide it.\n\n" \
${_opt}
exit -1
fi
set -u
}

6
lib/builtin/gettext.bash Normal file
View File

@@ -0,0 +1,6 @@
#!/usr/bin/bash
LC_MESSAGES=${LANG}
TEXTDOMAIN="dbtool"
TEXTDOMAINDIR="${PWD}/locale"
INTERACTIVE=false

229
lib/builtin/iniparse.bash Normal file
View File

@@ -0,0 +1,229 @@
#!/bin/bash
#
# based on http://theoldschooldevops.com/2008/02/09/bash-ini-parser/
#
PREFIX="cfg_section_"
function debug {
if ! [ "x$BASH_INI_PARSER_DEBUG" == "x" ]
then
echo
echo --start-- $*
echo "${ini[*]}"
echo --end--
echo
fi
}
function cfg_parser {
shopt -p extglob &> /dev/null
CHANGE_EXTGLOB=$?
if [ $CHANGE_EXTGLOB = 1 ]
then
shopt -s extglob
fi
ini="$(<$1)" # read the file
ini=${ini//$'\r'/} # remove linefeed i.e dos2unix
ini="${ini//[/\\[}"
debug "escaped ["
ini="${ini//]/\\]}"
debug "escaped ]"
OLDIFS="$IFS"
IFS=$'\n' && ini=( ${ini} ) # convert to line-array
debug
ini=( ${ini[*]/#*([[:space:]]);*/} )
debug "removed ; comments"
ini=( ${ini[*]/#*([[:space:]])\#*/} )
debug "removed # comments"
ini=( ${ini[*]/#+([[:space:]])/} ) # remove init whitespace
debug "removed initial whitespace"
ini=( ${ini[*]/%+([[:space:]])/} ) # remove ending whitespace
debug "removed ending whitespace"
ini=( ${ini[*]/%+([[:space:]])\\]/\\]} ) # remove non meaningful whitespace after sections
debug "removed whitespace after section name"
if [ $BASH_VERSINFO == 3 ]
then
ini=( ${ini[*]/+([[:space:]])=/=} ) # remove whitespace before =
ini=( ${ini[*]/=+([[:space:]])/=} ) # remove whitespace after =
ini=( ${ini[*]/+([[:space:]])=+([[:space:]])/=} ) # remove whitespace around =
else
ini=( ${ini[*]/*([[:space:]])=*([[:space:]])/=} ) # remove whitespace around =
fi
debug "removed space around ="
ini=( ${ini[*]/#\\[/\}$'\n'"$PREFIX"} ) # set section prefix
debug
for ((i=0; i < "${#ini[@]}"; i++))
do
line="${ini[i]}"
if [[ "$line" =~ $PREFIX.+ ]]
then
ini[$i]=${line// /_}
fi
done
debug "subsections"
ini=( ${ini[*]/%\\]/ \(} ) # convert text2function (1)
debug
ini=( ${ini[*]/=/=\( } ) # convert item to array
debug
ini=( ${ini[*]/%/ \)} ) # close array parenthesis
debug
ini=( ${ini[*]/%\\ \)/ \\} ) # the multiline trick
debug
ini=( ${ini[*]/%\( \)/\(\) \{} ) # convert text2function (2)
debug
ini=( ${ini[*]/%\} \)/\}} ) # remove extra parenthesis
ini=( ${ini[*]/%\{/\{$'\n''cfg_unset ${FUNCNAME/#'$PREFIX'}'$'\n'} ) # clean previous definition of section
debug
ini[0]="" # remove first element
debug
ini[${#ini[*]} + 1]='}' # add the last brace
debug
eval "$(echo "${ini[*]}")" # eval the result
EVAL_STATUS=$?
if [ $CHANGE_EXTGLOB = 1 ]
then
shopt -u extglob
fi
IFS="$OLDIFS"
return $EVAL_STATUS
}
function cfg_writer {
local item fun newvar vars
SECTION=$1
OLDIFS="$IFS"
IFS=' '$'\n'
if [ -z "$SECTION" ]
then
fun="$(declare -F)"
else
fun="$(declare -F $PREFIX$SECTION)"
if [ -z "$fun" ]
then
echo "section $SECTION not found" 1>&2
exit 1
fi
fi
fun="${fun//declare -f/}"
for f in $fun; do
[ "${f#$PREFIX}" == "${f}" ] && continue
item="$(declare -f ${f})"
item="${item##*\{}" # remove function definition
item="${item##*FUNCNAME*$PREFIX\};}" # remove clear section
item="${item/FUNCNAME\/#$PREFIX;}" # remove line
item="${item/%\}}" # remove function close
item="${item%)*}" # remove everything after parenthesis
item="${item});" # add close parenthesis
vars=""
while [ "$item" != "" ]
do
newvar="${item%%=*}" # get item name
vars="$vars$newvar" # add name to collection
item="${item#*;}" # remove readed line
done
vars=$(echo "$vars" | sort -u) # remove duplication
eval $f
echo "[${f#$PREFIX}]" # output section
for var in $vars; do
eval 'local length=${#'$var'[*]}' # test if var is an array
if [ $length == 1 ]
then
echo $var=\"${!var}\" #output var
else
echo ";$var is an array" # add comment denoting var is an array
eval 'echo $var=\"${'$var'[*]}\"' # output array var
fi
done
done
IFS="$OLDIFS"
}
function cfg_unset {
local item fun newvar vars
SECTION=$1
OLDIFS="$IFS"
IFS=' '$'\n'
if [ -z "$SECTION" ]
then
fun="$(declare -F)"
else
fun="$(declare -F $PREFIX$SECTION)"
if [ -z "$fun" ]
then
echo "section $SECTION not found" 1>&2
return
fi
fi
fun="${fun//declare -f/}"
for f in $fun; do
[ "${f#$PREFIX}" == "${f}" ] && continue
item="$(declare -f ${f})"
item="${item##*\{}" # remove function definition
item="${item##*FUNCNAME*$PREFIX\};}" # remove clear section
item="${item/%\}}" # remove function close
item="${item%)*}" # remove everything after parenthesis
item="${item});" # add close parenthesis
vars=""
while [ "$item" != "" ]
do
newvar="${item%%=*}" # get item name
vars="$vars $newvar" # add name to collection
item="${item#*;}" # remove readed line
done
for var in $vars; do
unset $var
done
done
IFS="$OLDIFS"
}
function cfg_clear {
SECTION=$1
OLDIFS="$IFS"
IFS=' '$'\n'
if [ -z "$SECTION" ]
then
fun="$(declare -F)"
else
fun="$(declare -F $PREFIX$SECTION)"
if [ -z "$fun" ]
then
echo "section $SECTION not found" 1>&2
exit 1
fi
fi
fun="${fun//declare -f/}"
for f in $fun; do
[ "${f#$PREFIX}" == "${f}" ] && continue
unset -f ${f}
done
IFS="$OLDIFS"
}
function cfg_update {
SECTION=$1
VAR=$2
OLDIFS="$IFS"
IFS=' '$'\n'
fun="$(declare -F $PREFIX$SECTION)"
if [ -z "$fun" ]
then
echo "section $SECTION not found" 1>&2
exit 1
fi
fun="${fun//declare -f/}"
item="$(declare -f ${fun})"
#item="${item##* $VAR=*}" # remove var declaration
item="${item/%\}}" # remove function close
item="${item}
$VAR=(${!VAR})
"
item="${item}
}" # close function again
eval "function $item"
}
# vim: filetype=sh

16
lib/builtin/ui.bash Normal file
View File

@@ -0,0 +1,16 @@
#!/usr/bin/bash
set -euo pipefail
# -- UI Class -----------------------------------------------------------------
ui.__load__() {
# Default User Interface
: ${ui:=cli}
# Loads the UI
source ${libdir}/ui/${ui}.bash
}
ui.__load__
# vim: ts=4:sw=4:sts=4:et

26
lib/commands/create.bash Normal file
View File

@@ -0,0 +1,26 @@
#!/usr/bin/bash
set -euo pipefail
# -- Create Class --------------------------------------------------------------
create.run() {
local _plugin=$(basename ${host[create_tool]})
${ui}.info && ${ui}.tab 2 && \
${ui}.subitem $"Loading plugin: "
test -f ${libdir}/plugins/${_plugin}.bash && \
${ui}.emphasis $"%s " ${_plugin} && \
source ${libdir}/plugins/${_plugin}.bash
eval \${_plugin}.run
}
create.__load__() {
unset -v cmd
options+='c:'
arguments['c']="cmd"
}
create.__load__
# vim: ts=4:sw=4:sts=4:et

26
lib/commands/drop.bash Normal file
View File

@@ -0,0 +1,26 @@
#!/usr/bin/bash
set -euo pipefail
# -- Create Class --------------------------------------------------------------
drop.run() {
local _plugin=$(basename ${host[drop_tool]})
${ui}.info && ${ui}.tab 2 && \
${ui}.subitem $"Loading plugin: "
test -f ${libdir}/plugins/${_plugin}.bash && \
${ui}.emphasis $"%s " ${_plugin} && \
source ${libdir}/plugins/${_plugin}.bash
eval \${_plugin}.run
}
drop.__load__() {
unset -v cmd
options+='c:'
arguments['c']="cmd"
}
drop.__load__
# vim: ts=4:sw=4:sts=4:et

26
lib/commands/dump.bash Normal file
View File

@@ -0,0 +1,26 @@
#!/usr/bin/bash
set -euo pipefail
# -- Dump Class ---------------------------------------------------------------
dump.run() {
local _plugin=$(basename ${host[dump_tool]})
${ui}.info && ${ui}.tab 2 && \
${ui}.subitem $"Loading plugin: "
test -f ${libdir}/plugins/${_plugin}.bash && \
${ui}.emphasis $"%s " ${_plugin} && \
source ${libdir}/plugins/${_plugin}.bash
eval \${_plugin}.run
}
dump.__load__() {
unset -v cmd
options+='c:'
arguments['c']="cmd"
}
dump.__load__
# vim: ts=4:sw=4:sts=4:et

26
lib/commands/restore.bash Normal file
View File

@@ -0,0 +1,26 @@
#!/usr/bin/bash
set -euo pipefail
# -- Restore Class ------------------------------------------------------------
restore.run() {
local _plugin=$(basename ${host[restore_tool]})
${ui}.info && ${ui}.tab 2 && \
${ui}.subitem $"Loading plugin: "
test -f ${libdir}/plugins/${_plugin}.bash && \
${ui}.emphasis $"%s " ${_plugin} && \
source ${libdir}/plugins/${_plugin}.bash
eval \${_plugin}.run
}
restore.__load__() {
unset -v cmd
options+='c:'
arguments['c']="cmd"
}
restore.__load__
# vim: ts=4:sw=4:sts=4:et

15
lib/entities/dblist.bash Normal file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/bash
set -euo pipefail
# -- Host Class ---------------------------------------------------------------
dblist.__load__() {
unset -v dblist
options+='d:'
arguments['d']="dblist"
}
dblist.__load__
# vim: ts=4:sw=4:sts=4:et

120
lib/entities/host.bash Normal file
View File

@@ -0,0 +1,120 @@
#!/usr/bin/bash
set -euo pipefail
# -- Host Class ---------------------------------------------------------------
host.__load__() {
source ${libdir}/builtin/iniparse.bash
unset -v host
options+='h:'
arguments['h']="host"
}
host.set() {
function __in_array() {
local a=${@:2}
[[ " ${a[*]} " =~ " ${1} " ]] \
&& return 0 \
|| return 1
}
function _validate_host() {
__in_array ${host:-} "${_HOSTS[@]}" || \
(
${ui}.error
${ui}.print $"Invalid host: %s: " ${host:-'(empty)'}
kill -TERM ${$} 2>&1 > /dev/null
)
return 0
}
function _get_config() {
local _host=${host}
set +eu
cfg_parser "${cfgdir}/hosts/${_host}.ini"
cfg_section_engines
local _postgresql=${postgresql}
local _mysql=${mysql}
if [[ "${_postgresql,,}" == "true" ]]; then
cfg_section_postgresql
query_tool='psql'
create_tool='createdb'
drop_tool='dropdb'
dump_tool='pg_dump'
restore_tool='pg_restore'
fi
declare -Ag host=(
[name]=${_host}
[db_env]=${env}
[db_host]=${db_host}
[db_port]=${db_port}
[db_user]=${db_user}
[db_pass]=${db_password}
)
exec=
eval cfg_section_${query_tool}
host+=(
[query_tool]=${exec:-$(which ${query_tool})}
[query_extraopts]=${extraopts}
[query_ext]=${extension}
)
exec=
host+=(
[create_tool]=${exec:-$(which ${create_tool})}
[drop_tool]=${exec:-$(which ${drop_tool})}
)
exec=
eval cfg_section_${dump_tool}
host+=(
[dump_tool]=${exec:-$(which ${dump_tool})}
[dump_format]=${dump_format:-'custom'} # postgresql only
[dump_extraopts]=${extraopts}
[dump_ext]=${extension}
)
exec=
eval cfg_section_${restore_tool}
host+=(
[restore_tool]=${exec:-$(which ${restore_tool})}
[restore_format]=${dump_format:-'custom'} # postgresql only
[restore_extraopts]=${extraopts}
[restore_ext]=${extension}
)
set -eu
return 0
}
declare -a _HOSTS=(
$(find ${cfgdir}/hosts/ \
-type f \
-name \*.ini \
-exec basename {} \; \
| sed 's/.ini//g'
)
)
_validate_host
_get_config
hostname=${host[name]}
if [[ ! -z "${hostname}" ]]; then
${ui}.debug 'line' 'compact'
${ui}.info
${ui}.item $"Selecting current server [host %s %s]\n" \
$(${ui}.color gold; ${ui}.arrow right; ${ui}.color) \
${hostname}
fi
}
host.__load__
# vim: ts=4:sw=4:sts=4:et

120
lib/plugins/createdb.bash Normal file
View File

@@ -0,0 +1,120 @@
#!/usr/bin/bash
set -euo pipefail
# -- CreateDb Class ------------------------------------------------------------
createdb.__load__() {
return 0
}
createdb.run() {
local _logdir=$(pwd)/logs
local _pgpassfile=~/.pgpass
declare -A pids=()
function __write_pgpass() {
if [[ ! -z "${host[name]}" ]]; then
touch ${_pgpassfile} && \
chmod 0600 ${_pgpassfile}
printf "%s:%s:*:%s:%s" \
${host[db_host]} \
${host[db_port]} \
${host[db_user]} \
${host[db_pass]} \
> ${_pgpassfile}
fi
}
function __pre() {
${ui}.debug 'text' '\n'
mkdir -p ${_logdir}
__write_pgpass
echo
}
function __post() {
rm -rf ${_pgpassfile}
}
function __wait_processes() {
${ui}.info && \
${ui}.item $"Waiting for processes to complete\n"
${ui}.line 'compact'
while true; do
alive_pids=()
for _pid in "${!pids[@]}"; do
_db=${pids[${_pid}]}
kill -0 "${_pid}" 2>/dev/null \
&& _etime[${_pid}]=$(ps -o etimes= -p ${_pid} || :) \
&& alive_pids+="${_pid} "
${ui}.info && \
${ui}.print '%s ' $(${ui}.color green; echo "[createdb]")
${ui}.color && \
${ui}.subitem $"process still running "
${ui}.print '%s ' \
$(${ui}.color gold; ${ui}.arrow right; ${ui}.color)
${ui}.print "[db=${_db}, pid=${_pid}, etime=%ss]\n" \
${_etime[${_pid}]}
done
[ ${#alive_pids[@]} -eq 0 ] && break
sleep 5
done
${ui}.print '\n'
${ui}.info && \
${ui}.print '%s ' $(${ui}.color green; echo "[createdb]")
${ui}.color && ${ui}.subitem $"all processes terminated\n"
${ui}.line 'compact'
}
function __createdb() {
local _createdb=$(which createdb || exit -1)
local _logfile=create-${db}-$(date +"%Y%m%d%H%M%S").log
local _cmd="${_createdb} \
-h ${host[db_host]} \
-p ${host[db_port]} \
-U ${host[db_user]} \
${host[create_extraopts]:-} \
-O ${host[db_user]} ${db}"
_cmd=$(echo -ne ${_cmd})
${ui}.debug 'line' 'compact'
${ui}.debug && ${ui}.tab 2
${ui}.debug 'subitem' $"Running command:\n"
#${ui}.debug 'text' "$(${ui}.color gold)${_cmd//${_restoredir}\//} \
${ui}.debug 'text' "$(${ui}.color gold)${_cmd} \
$(${ui}.color)" \
| fold -sw 60 \
| sed "s/^/$(printf '%14s' ' ')/g"
${ui}.debug 'text' '\n'
${ui}.debug && ${ui}.tab 2
${ui}.debug 'subitem' $"Process will run in backgroud [logfile=%s]\n" \
${_logfile}
${_cmd} > ${_logdir}/${_logfile} 2>&1 &
pids+=([$!]=${db})
}
function __run() {
${ui}.debug 'text' '\n'
dbs="${dblist}"
test -f ${cfgdir}/databases/${dblist}.txt && \
dbs=$(< ${cfgdir}/databases/${dblist}.txt);
for db in ${dbs}; do
${ui}.info && \
${ui}.item $"Starting %s %s [host=%s, db=%s]\n" \
"createdb" \
$(${ui}.color gold; ${ui}.arrow right; ${ui}.color) \
${host[name]} \
${db}
__createdb
${ui}.debug 'text' '\n'
done
__wait_processes
}
__pre
__run
__post
}
createdb.__load__
# vim: ts=4:sw=4:sts=4:et

120
lib/plugins/dropdb.bash Normal file
View File

@@ -0,0 +1,120 @@
#!/usr/bin/bash
set -euo pipefail
# -- DropDb Class --------------------------------------------------------------
dropdb.__load__() {
return 0
}
dropdb.run() {
local _logdir=$(pwd)/logs
local _pgpassfile=~/.pgpass
declare -A pids=()
function __write_pgpass() {
if [[ ! -z "${host[name]}" ]]; then
touch ${_pgpassfile} && \
chmod 0600 ${_pgpassfile}
printf "%s:%s:*:%s:%s" \
${host[db_host]} \
${host[db_port]} \
${host[db_user]} \
${host[db_pass]} \
> ${_pgpassfile}
fi
}
function __pre() {
${ui}.debug 'text' '\n'
mkdir -p ${_logdir}
__write_pgpass
echo
}
function __post() {
rm -rf ${_pgpassfile}
}
function __wait_processes() {
${ui}.info && \
${ui}.item $"Waiting for processes to complete\n"
${ui}.line 'compact'
while true; do
alive_pids=()
for _pid in "${!pids[@]}"; do
_db=${pids[${_pid}]}
kill -0 "${_pid}" 2>/dev/null \
&& _etime[${_pid}]=$(ps -o etimes= -p ${_pid} || :) \
&& alive_pids+="${_pid} "
${ui}.info && \
${ui}.print '%s ' $(${ui}.color green; echo "[dropdb]")
${ui}.color && \
${ui}.subitem $"process still running "
${ui}.print '%s ' \
$(${ui}.color gold; ${ui}.arrow right; ${ui}.color)
${ui}.print "[db=${_db}, pid=${_pid}, etime=%ss]\n" \
${_etime[${_pid}]}
done
[ ${#alive_pids[@]} -eq 0 ] && break
sleep 5
done
${ui}.print '\n'
${ui}.info && \
${ui}.print '%s ' $(${ui}.color green; echo "[dropdb]")
${ui}.color && ${ui}.subitem $"all processes terminated\n"
${ui}.line 'compact'
}
function __dropdb() {
local _dropdb=$(which dropdb || exit -1)
local _logfile=drop-${db}-$(date +"%Y%m%d%H%M%S").log
local _cmd="${_dropdb} \
-h ${host[db_host]} \
-p ${host[db_port]} \
-U ${host[db_user]} \
${host[drop_extraopts]:-} \
-f --if-exists ${db}"
_cmd=$(echo -ne ${_cmd})
${ui}.debug 'line' 'compact'
${ui}.debug && ${ui}.tab 2
${ui}.debug 'subitem' $"Running command:\n"
#${ui}.debug 'text' "$(${ui}.color gold)${_cmd//${_restoredir}\//} \
${ui}.debug 'text' "$(${ui}.color gold)${_cmd} \
$(${ui}.color)" \
| fold -sw 60 \
| sed "s/^/$(printf '%14s' ' ')/g"
${ui}.debug 'text' '\n'
${ui}.debug && ${ui}.tab 2
${ui}.debug 'subitem' $"Process will run in backgroud [logfile=%s]\n" \
${_logfile}
${_cmd} > ${_logdir}/${_logfile} 2>&1 &
pids+=([$!]=${db})
}
function __run() {
${ui}.debug 'text' '\n'
dbs="${dblist}"
test -f ${cfgdir}/databases/${dblist}.txt && \
dbs=$(< ${cfgdir}/databases/${dblist}.txt);
for db in ${dbs}; do
${ui}.info && \
${ui}.item $"Starting %s %s [host=%s, db=%s]\n" \
"dropdb" \
$(${ui}.color gold; ${ui}.arrow right; ${ui}.color) \
${host[name]} \
${db}
__dropdb
${ui}.debug 'text' '\n'
done
__wait_processes
}
__pre
__run
__post
}
dropdb.__load__
# vim: ts=4:sw=4:sts=4:et

123
lib/plugins/pg_dump.bash Normal file
View File

@@ -0,0 +1,123 @@
#!/usr/bin/bash
set -euo pipefail
# -- PGDump Class -------------------------------------------------------------
pg_dump.__load__() {
return 0
}
pg_dump.run() {
local _dumpdir=$(pwd)/export/${host[name]}
local _logdir=$(pwd)/logs
local _pgpassfile=~/.pgpass
declare -A pids=()
function __write_pgpass() {
if [[ ! -z "${host[name]}" ]]; then
touch ${_pgpassfile} && \
chmod 0600 ${_pgpassfile}
printf "%s:%s:*:%s:%s" \
${host[db_host]} \
${host[db_port]} \
${host[db_user]} \
${host[db_pass]} \
> ${_pgpassfile}
fi
}
function __pre() {
__write_pgpass
mkdir -p ${_dumpdir} ${_logdir}
echo
}
function __post() {
rm -rf ${_pgpassfile}
}
function __wait_processes() {
${ui}.info && \
${ui}.item $"Waiting for processes to complete\n"
${ui}.line 'compact'
while true; do
alive_pids=()
for _pid in "${!pids[@]}"; do
_db=${pids[${_pid}]}
kill -0 "${_pid}" 2>/dev/null \
&& _etime[${_pid}]=$(ps -o etimes= -p ${_pid} || :) \
&& alive_pids+="${_pid} "
${ui}.info && \
${ui}.print '%s ' $(${ui}.color green; echo "[pg_dump]")
${ui}.color && \
${ui}.subitem $"process still running "
${ui}.print '%s ' \
$(${ui}.color gold; ${ui}.arrow right; ${ui}.color)
${ui}.print "[db=${_db}, pid=${_pid}, etime=%ss]\n" \
${_etime[${_pid}]}
done
[ ${#alive_pids[@]} -eq 0 ] && break
sleep 5
done
${ui}.print '\n'
${ui}.info && \
${ui}.print '%s ' $(${ui}.color green; echo "[pg_dump]")
${ui}.color && ${ui}.subitem $"all processes terminated\n"
${ui}.line 'compact'
}
function __pg_dump() {
local _pg_dump=$(which pg_dump || exit -1)
local _ext=${host[dump_ext]:-pgc}
local _dumpfile=${db}-$(date +"%Y%m%d%H%M%S").${_ext}
local _logfile=export-${db}-$(date +"%Y%m%d%H%M%S").${_ext}.log
local _cmd="${_pg_dump} \
-h ${host[db_host]} \
-p ${host[db_port]} \
-U ${host[db_user]} \
-F ${host[dump_format]:0:1} \
${host[dump_extraopts]:-} \
-O -v -d ${db} \
-f ${_dumpdir}/${_dumpfile}"
_cmd=$(echo -ne ${_cmd})
${ui}.debug 'line' 'compact'
${ui}.debug && ${ui}.tab 2
${ui}.debug 'subitem' $"Running command:\n"
${ui}.debug 'text' "$(${ui}.color gold)${_cmd//${_dumpdir}\//} \
$(${ui}.color)" \
| fold -sw 60 \
| sed "s/^/$(printf '%14s' ' ')/g"
${ui}.debug 'text' '\n'
${ui}.debug && ${ui}.tab 2
${ui}.debug 'subitem' $"Process will run in backgroud [logfile=%s]\n" \
${_logfile}
${_cmd} > ${_logdir}/${_logfile} 2>&1 &
pids+=([$!]=${db})
}
function __run() {
${ui}.debug 'text' '\n'
dbs="${dblist}"
test -f ${cfgdir}/databases/${dblist}.txt && \
dbs=$(< ${cfgdir}/databases/${dblist}.txt);
for db in ${dbs}; do
${ui}.info && \
${ui}.item $"Starting %s %s [host=%s, db=%s]\n" \
"pg_dump" \
$(${ui}.color gold; ${ui}.arrow right; ${ui}.color) \
${host[name]} \
${db}
__pg_dump
${ui}.debug 'text' '\n'
done
__wait_processes
}
__pre
__run
__post
}
pg_dump.__load__
# vim: ts=4:sw=4:sts=4:et

135
lib/plugins/pg_restore.bash Normal file
View File

@@ -0,0 +1,135 @@
#!/usr/bin/bash
set -euo pipefail
# -- PGDump Class -------------------------------------------------------------
pg_restore.__load__() {
return 0
}
pg_restore.run() {
local _restoredir=$(pwd)/import/${host[name]}
local _logdir=$(pwd)/logs
local _pgpassfile=~/.pgpass
declare -A pids=()
function __write_pgpass() {
if [[ ! -z "${host[name]}" ]]; then
touch ${_pgpassfile} && \
chmod 0600 ${_pgpassfile}
printf "%s:%s:*:%s:%s" \
${host[db_host]} \
${host[db_port]} \
${host[db_user]} \
${host[db_pass]} \
> ${_pgpassfile}
fi
}
function __pre() {
${ui}.debug 'text' '\n'
mkdir -p ${_restoredir} ${_logdir}
dbs="${dblist}"
test -f ${cfgdir}/databases/${dblist}.txt && \
dbs=$(< ${cfgdir}/databases/${dblist}.txt);
for db in ${dbs}; do
# Check if dump file exists
[ ! -f ${_restoredir}/${db}.${host[restore_ext]} ] && \
${ui}.error && \
${ui}.print $"Dump file not found: %s\n" \
${db}.${host[restore_ext]} && \
exit -1
done
__write_pgpass
echo
}
function __post() {
rm -rf ${_pgpassfile}
}
function __wait_processes() {
${ui}.info && \
${ui}.item $"Waiting for processes to complete\n"
${ui}.line 'compact'
while true; do
alive_pids=()
for _pid in "${!pids[@]}"; do
_db=${pids[${_pid}]}
kill -0 "${_pid}" 2>/dev/null \
&& _etime[${_pid}]=$(ps -o etimes= -p ${_pid} || :) \
&& alive_pids+="${_pid} "
${ui}.info && \
${ui}.print '%s ' $(${ui}.color green; echo "[pg_restore]")
${ui}.color && \
${ui}.subitem $"process still running "
${ui}.print '%s ' \
$(${ui}.color gold; ${ui}.arrow right; ${ui}.color)
${ui}.print "[db=${_db}, pid=${_pid}, etime=%ss]\n" \
${_etime[${_pid}]}
done
[ ${#alive_pids[@]} -eq 0 ] && break
sleep 5
done
${ui}.print '\n'
${ui}.info && \
${ui}.print '%s ' $(${ui}.color green; echo "[pg_restore]")
${ui}.color && ${ui}.subitem $"all processes terminated\n"
${ui}.line 'compact'
}
function __pg_restore() {
local _pg_restore=$(which pg_restore || exit -1)
local _ext=${host[restore_ext]:-pgc}
local _restorefile=${db}.${_ext}
local _logfile=import-${db}-$(date +"%Y%m%d%H%M%S").${_ext}.log
local _cmd="${_pg_restore} \
-h ${host[db_host]} \
-p ${host[db_port]} \
-U ${host[db_user]} \
-F ${host[restore_format]:0:1} \
${host[restore_extraopts]:-} \
-O -v -d ${db} \
${_restoredir}/${_restorefile}"
_cmd=$(echo -ne ${_cmd})
${ui}.debug 'line' 'compact'
${ui}.debug && ${ui}.tab 2
${ui}.debug 'subitem' $"Running command:\n"
${ui}.debug 'text' "$(${ui}.color gold)${_cmd//${_restoredir}\//} \
$(${ui}.color)" \
| fold -sw 60 \
| sed "s/^/$(printf '%14s' ' ')/g"
${ui}.debug 'text' '\n'
${ui}.debug && ${ui}.tab 2
${ui}.debug 'subitem' $"Process will run in backgroud [logfile=%s]\n" \
${_logfile}
${_cmd} > ${_logdir}/${_logfile} 2>&1 &
pids+=([$!]=${db})
}
function __run() {
${ui}.debug 'text' '\n'
dbs="${dblist}"
test -f ${cfgdir}/databases/${dblist}.txt && \
dbs=$(< ${cfgdir}/databases/${dblist}.txt);
for db in ${dbs}; do
${ui}.info && \
${ui}.item $"Starting %s %s [host=%s, db=%s]\n" \
"pg_restore" \
$(${ui}.color gold; ${ui}.arrow right; ${ui}.color) \
${host[name]} \
${db}
__pg_restore
${ui}.debug 'text' '\n'
done
__wait_processes
}
__pre
__run
__post
}
pg_restore.__load__
# vim: ts=4:sw=4:sts=4:et

160
lib/ui/cli.bash Normal file
View File

@@ -0,0 +1,160 @@
#!/usr/bin/bash
set -euo pipefail
# -- CLI Class ----------------------------------------------------------------
cli.__load__() {
NO_FMT="\033[0m"
F_BOLD="\033[1m"
C_NONE="\033[0m"
C_RED="\033[38;5;9m"
C_BLUE="\033[38;5;12m"
C_GREEN="\033[38;5;2m"
C_ORANGE="\033[38;5;208m"
C_GOLD="\033[38;5;220m"
C_PURPLE="\033[38;5;5m"
S_INFO="${C_BLUE}\u2691 [info] ${NO_FMT} "
S_SUCCESS="${C_GREEN}\u2691 [success] ${NO_FMT}\u2713 "
S_DEBUG="${C_PURPLE}\u2691 [debug]${NO_FMT} "
S_WARN="${C_ORANGE}\u2691 [warning] ${NO_FMT} "
S_ERROR="${C_RED}\u2691 [error] ${NO_FMT}\u2715 "
U_ITEM="\u2192"
U_SUBITEM="\u21E2"
#U_SUBITEM="\u21B3"
}
cli.init() {
function __write_header() {
#clear && echo
printf $"${@:1:1}" \
$(echo -ne ${F_BOLD}${C_BLUE}${@:2:1}${NO_FMT}) \
$(echo -ne ${C_ORANGE}${@:3}${NO_FMT}) && \
for i in $(seq 1 ${cols}); do \
printf "%s" $(echo -ne "\u2581"); \
sleep .005; \
done && \
printf "%2s\n"
}
function _init() {
__write_header "${@}"
}
cols=$(tput cols)
_init "${@}"
}
cli.line() {
[ -z "${1:-}" ] && printf "%s\n"
for i in $(seq 1 ${cols}); do \
printf "%s" $(echo -ne "\u2504${NO_FMT}"); \
done && \
[ -z "${1:-}" ] && printf "%2s\n"
return 0
}
cli.move_to_col() {
echo -ne "echo -en \\033[${1:-0}G"
}
cli.info() {
cli.move_to_col 0
echo -ne "${S_INFO}"
}
cli.warn() {
cli.move_to_col 0
echo -ne "${S_WARN}"
}
cli.error() {
cli.move_to_col 0
echo -ne "${S_ERROR}"
}
cli.print() {
printf $"${@:1:1}" ${@:2}
}
cli.tab() {
printf "%${1:-2}s" ' '
}
cli.item() {
echo -ne "${U_ITEM} "
cli.print "${@}"
}
cli.subitem() {
echo -ne "${U_SUBITEM} "
cli.print "${@}"
}
cli.emphasis() {
echo -ne "${F_BOLD}${C_BLUE}"
cli.print "${@}"
echo -ne "${NO_FMT}"
}
cli.debug() {
if [ "${debug:-,,}" == "true" ]; then
[ ${#@} -lt 1 ] && \
cli.move_to_col 0 && \
echo -ne "${S_DEBUG}"
[ "${1:-z}" == "text" ] && \
cli.print "${@:2}"
[ "${1:-z}" == "line" ] && \
cli.move_to_col 0 && \
cli.line "${@:2}"
[ "${1:-z}" == "item" ] && \
cli.item "${@:2}"
[ "${1:-z}" == "subitem" ] && \
cli.subitem "${@:2}"
[ "${1:-z}" == "emphasis" ] && \
cli.emphasis "${@:2}"
fi
set -e # why we need to set it again?
}
cli.dbg_item() {
[ "${debug:-,,}" == "true" ] && \
echo -ne "${U_ITEM} " && \
cli.print "${@}"
}
cli.dbg_subitem() {
[ "${debug:-,,}" == "true" ] && \
echo -ne "${U_SUBITEM} " && \
cli.print "${@}"
}
cli.color() {
color=${1:-NONE}
eval "echo -ne \${C_${color^^}}"
}
cli.arrow() {
case ${1:-} in
top) arrow='\u21E1';;
left) arrow='\u21E0' ;;
bottom) arrow='\u21E3' ;;
right) arrow='\u21E2' ;;
esac
echo -ne "${arrow}"
}
cli.__load__
# vim: ts=4:sw=4:sts=4:et