diff --git a/conf/dbScripts/extractData b/conf/dbScripts/extractData index ffe75f11..2b208e9a 100755 --- a/conf/dbScripts/extractData +++ b/conf/dbScripts/extractData @@ -1,12 +1,12 @@ #!/usr/bin/env bash ############################################################################### -# GENERATED FACADE FROM https://github.com/fchastanet/bash-tools/tree/master/src/_binaries/DbScriptAllDatabases/extractData.sh +# GENERATED FROM https://github.com/fchastanet/bash-tools-framework/tree/master/src/_binaries/Database/extractData/binary-extractData.yaml # DO NOT EDIT IT # @generated ############################################################################### # shellcheck disable=SC2288,SC2034 -# BIN_FILE=${BASH_TOOLS_ROOT_DIR}/conf/dbScripts/extractData -# FACADE + + # ensure that no user aliases could interfere with # commands used in this script @@ -61,13 +61,6 @@ interruptManagement() { kill -s INT "$$" } trap interruptManagement INT -SCRIPT_NAME=${0##*/} -REAL_SCRIPT_FILE="$(readlink -e "$(realpath "${BASH_SOURCE[0]}")")" -if [[ -n "${EMBED_CURRENT_DIR}" ]]; then - CURRENT_DIR="${EMBED_CURRENT_DIR}" -else - CURRENT_DIR="${REAL_SCRIPT_FILE%/*}" -fi ################################################ # Temp dir management @@ -101,6 +94,20 @@ cleanOnExit() { } trap cleanOnExit EXIT HUP QUIT ABRT TERM + +SCRIPT_NAME=${0##*/} +REAL_SCRIPT_FILE="$(readlink -e "$(realpath "${BASH_SOURCE[0]}")")" +if [[ -n "${EMBED_CURRENT_DIR}" ]]; then + CURRENT_DIR="${EMBED_CURRENT_DIR}" +else + CURRENT_DIR="${REAL_SCRIPT_FILE%/*}" +fi +FRAMEWORK_ROOT_DIR="$(cd "${CURRENT_DIR}/../.." && pwd -P)" +FRAMEWORK_SRC_DIR="${FRAMEWORK_ROOT_DIR}/src" +FRAMEWORK_BIN_DIR="${FRAMEWORK_ROOT_DIR}/bin" +FRAMEWORK_VENDOR_DIR="${FRAMEWORK_ROOT_DIR}/vendor" +FRAMEWORK_VENDOR_BIN_DIR="${FRAMEWORK_ROOT_DIR}/vendor/bin" + # @description Log namespace provides 2 kind of functions # - Log::display* allows to display given message with # given display level @@ -131,31 +138,159 @@ export __VERBOSE_LEVEL_DEBUG=2 # @description verbose level info export __VERBOSE_LEVEL_TRACE=3 -# @description Display message using info color (bg light blue/fg white) -# @arg $1 message:String the message to display -# @env DISPLAY_DURATION int (default 0) if 1 display elapsed time information between 2 info logs -# @env LOG_CONTEXT String allows to contextualize the log -Log::displayInfo() { - local type="${2:-INFO}" - if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_INFO)); then - Log::computeDuration - echo -e "${__INFO_COLOR}${type} - ${LOG_CONTEXT:-}${LOG_LAST_DURATION_STR:-}${1}${__RESET_COLOR}" >&2 + +# @description concatenate each element of an array with a separator +# but wrapping text when line length is more than provided argument +# The algorithm will try not to cut the array element if it can. +# - if an arg can be placed on current line it will be, +# otherwise current line is printed and arg is added to the new +# current line +# - Empty arg is interpreted as a new line. +# - Add \r to arg in order to force break line and avoid following +# arg to be concatenated with current arg. +# +# @arg $1 glue:String +# @arg $2 maxLineLength:int +# @arg $3 indentNextLine:int +# @arg $@ array:String[] +Array::wrap2() { + local glue="${1-}" + local -i glueLength="${#glue}" + shift || true + local -i maxLineLength=$1 + shift || true + local -i indentNextLine=$1 + shift || true + local indentStr="" + if ((indentNextLine > 0)); then + indentStr="$(head -c "${indentNextLine}" 0)); do + arg="$1" + shift || true + + # replace tab by 2 spaces + arg="${arg//$'\t'/ }" + # remove trailing spaces + arg="${arg%[[:blank:]]}" + if [[ "${arg}" = $'\n' || -z "${arg}" ]]; then + printCurrentLine + ((previousLineEmpty = 1)) + continue + else + if ((previousLineEmpty == 1)); then + printCurrentLine + fi + ((previousLineEmpty = 0)) || true + fi + # convert eol to args + mapfile -t additionalLines <<<"${arg}" + if ((${#additionalLines[@]} > 1)); then + set -- "${additionalLines[@]}" "$@" + continue + fi + + ((argLength = ${#arg})) || true + + # empty arg + if ((argLength == 0)); then + if ((isNewline == 0)); then + # isNewline = 0 means currentLine is not empty + printCurrentLine + fi + continue + fi + + if ((isNewline == 0)); then + glueLength="${#glue}" + else + glueLength="0" + fi + if ((currentLineLength + argLength + glueLength > maxLineLength)); then + if ((argLength + glueLength > maxLineLength)); then + # arg is too long to even fit on one line + # we have to split the arg on current and next line + local -i remainingLineLength + ((remainingLineLength = maxLineLength - currentLineLength - glueLength)) + appendToCurrentLine "${glue:0:${glueLength}}${arg:0:${remainingLineLength}}" "$((glueLength + remainingLineLength))" + printCurrentLine + arg="${arg:${remainingLineLength}}" + # remove leading spaces + arg="${arg##[[:blank:]]}" + + set -- "${arg}" "$@" + else + # the arg can fit on next line + printCurrentLine + appendToCurrentLine "${arg}" "${argLength}" + fi + else + appendToCurrentLine "${glue:0:${glueLength}}${arg}" "$((glueLength + argLength))" + fi + done + if [[ "${currentLine}" != "" ]] && [[ ! "${currentLine}" =~ ^[\ \t]+$ ]]; then + printCurrentLine + fi + ) | sed -E -e 's/[[:blank:]]+$//' } -# @description Display message using debug color (gray) -# @arg $1 message:String the message to display -# @env DISPLAY_DURATION int (default 0) if 1 display elapsed time information between 2 info logs -# @env LOG_CONTEXT String allows to contextualize the log -Log::displayDebug() { - if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_DEBUG)); then - Log::computeDuration - echo -e "${__DEBUG_COLOR}DEBUG - ${LOG_CONTEXT:-}${LOG_LAST_DURATION_STR:-}${1}${__RESET_COLOR}" >&2 - fi - Log::logDebug "$1" + +# @description check if command specified exists or return 1 +# with error and message if not +# +# @arg $1 commandName:String on which existence must be checked +# @arg $2 helpIfNotExists:String a help command to display if the command does not exist +# +# @exitcode 1 if the command specified does not exist +# @stderr diagnostic information + help if second argument is provided +Assert::commandExists() { + local commandName="$1" + local helpIfNotExists="$2" + + "${BASH_FRAMEWORK_COMMAND:-command}" -v "${commandName}" >/dev/null 2>/dev/null || { + Log::displayError "${commandName} is not installed, please install it" + if [[ -n "${helpIfNotExists}" ]]; then + Log::displayInfo "${helpIfNotExists}" + fi + return 1 + } + return 0 } + # @description exits with message if current user is root # @noargs # @exitcode 1 if current user is root @@ -166,86 +301,144 @@ Assert::expectNonRootUser() { fi } -# @description Display message using error color (red) and exit immediately with error status 1 -# @arg $1 message:String the message to display -# @env DISPLAY_DURATION int (default 0) if 1 display elapsed time information between 2 info logs -# @env LOG_CONTEXT String allows to contextualize the log -Log::fatal() { - Log::computeDuration - echo -e "${__ERROR_COLOR}FATAL - ${LOG_CONTEXT:-}${LOG_LAST_DURATION_STR:-}${1}${__RESET_COLOR}" >&2 - Log::logFatal "$1" - exit 1 -} - -# @description create a new db instance -# Returns immediately if the instance is already initialized -# -# @arg $1 instanceNewInstance:&Map (passed by reference) database instance to use -# @arg $2 dsn:String dsn profile - load the dsn.env profile deduced using rules defined in Conf::getAbsoluteFile -# -# @example -# declare -Agx dbInstance -# Database::newInstance dbInstance "default.local" -# -# @exitcode 1 if dns file not able to loaded -Database::newInstance() { - local -n instanceNewInstance=$1 - local dsn="$2" - local DSN_FILE - if [[ -v instanceNewInstance['INITIALIZED'] && "${instanceNewInstance['INITIALIZED']:-0}" == "1" ]]; then - return +# @description check if tty (interactive mode) is active +# @noargs +# @exitcode 1 if tty not active +# @env NON_INTERACTIVE if 1 consider as not interactive even if environment is interactive +# @env INTERACTIVE if 1 consider as interactive even if environment is not interactive +Assert::tty() { + if [[ "${NON_INTERACTIVE:-0}" = "1" ]]; then + return 1 fi + if [[ "${INTERACTIVE:-0}" = "1" ]]; then + return 0 + fi + tty -s +} - # final auth file generated from dns file - instanceNewInstance['AUTH_FILE']="" - instanceNewInstance['DSN_FILE']="" - # check dsn file - DSN_FILE="$(Conf::getAbsoluteFile "dsn" "${dsn}" "env")" || return 1 - Database::checkDsnFile "${DSN_FILE}" || return 1 - instanceNewInstance['DSN_FILE']="${DSN_FILE}" +# @description ignore exit code 141 from simple command pipes +# @example use with: +# local resultingStatus=0 +# local -a originalPipeStatus=() +# cmd1 | cmd2 || Bash::handlePipelineFailure resultingStatus originalPipeStatus || true +# [[ "${resultingStatus}" = "0" ]] +# @arg $1 resultingStatusCode:&int (passed by reference) (optional) resulting status code +# @arg $2 originalStatus:int[] (passed by reference) (optional) copy of original PIPESTATUS array +# @env PIPESTATUS assuming that this function is called like in the example provided +# @see https://unix.stackexchange.com/a/709880/582856 +Bash::handlePipelineFailure() { + local -a pipeStatusBackup=("${PIPESTATUS[@]}") + local -n handlePipelineFailure_resultingStatusCode=$1 + local -n handlePipelineFailure_originalStatus=$2 + # shellcheck disable=SC2034 + handlePipelineFailure_originalStatus=("${pipeStatusBackup[@]}") + handlePipelineFailure_resultingStatusCode=0 + local statusCode + for statusCode in "${pipeStatusBackup[@]}"; do + if ((statusCode == 141)); then + return 0 + elif ((statusCode > 0)); then + # shellcheck disable=SC2034 + handlePipelineFailure_resultingStatusCode="${statusCode}" + break + fi + done + return "${handlePipelineFailure_resultingStatusCode}" +} - # shellcheck source=/src/Database/testsData/dsn_valid.env - source "${instanceNewInstance['DSN_FILE']}" - instanceNewInstance['USER']="${USER}" - instanceNewInstance['PASSWORD']="${PASSWORD}" - instanceNewInstance['HOSTNAME']="${HOSTNAME}" - instanceNewInstance['PORT']="${PORT}" +# @description loads ~/.bash-tools/.env if available +# if not creates it from a default template +# else check if new options need to be added +BashTools::Conf::requireLoad() { +Linux::requireTarCommand +Compiler::Embed::extractFileFromBase64 \ + "${PERSISTENT_TMPDIR:-/tmp}/db87222729e0f1ed9c597a486a61a08c/bashToolsDefaultConfigTemplate" \ + "IyEvdXNyL2Jpbi9lbnYgYmFzaAojIHNoZWxsY2hlY2sgZGlzYWJsZT1TQzIwMzQKCiMgRGVmYXVsdCBzZXR0aW5ncwojIHlvdSBjYW4gb3ZlcnJpZGUgdGhlc2Ugc2V0dGluZ3MgYnkgY3JlYXRpbmcgJHtIT01FfS8uYmFzaC10b29scy8uZW52IGZpbGUKCiMjIwojIyMgRElTUExBWSBMZXZlbAojIyMgbWluaW11bSBsZXZlbCBvZiB0aGUgbWVzc2FnZXMgdGhhdCB3aWxsIGJlIGRpc3BsYXllZCBvbiBzY3JlZW4KIyMjCiMjIyAwOiBOTyBMT0cKIyMjIDE6IEVSUk9SCiMjIyAyOiBXQVJOSU5HCiMjIyAzOiBJTkZPCiMjIyA0OiBERUJVRwojIyMKQkFTSF9GUkFNRVdPUktfRElTUExBWV9MRVZFTD0ke0JBU0hfRlJBTUVXT1JLX0RJU1BMQVlfTEVWRUw6LTN9CgojIyMKIyMjIERJU1BMQVkgZHVyYXRpb24KIyMjIDA6IG5vIGR1cmF0aW9uIGlzIGRpc3BsYXllZCBvbiB0aGUgbWVzc2FnZXMKIyMjIDE6IGR1cmF0aW9uIGJldHdlZW4gcHJldmlvdXMgbWVzc2FnZSBhbmQgY3VycmVudCBpcyBkaXNwbGF5ZWQKIyMjIHdpdGggdGhlIG1lc3NhZ2UKIyMjCkRJU1BMQVlfRFVSQVRJT049JHtESVNQTEFZX0RVUkFUSU9OOjB9CgojIyMKIyMjIExvZyB0byBmaWxlCiMjIwojIyMgYWxsIGxvZyBtZXNzYWdlcyB3aWxsIGJlIHJlZGlyZWN0ZWQgdG8gbG9nIGZpbGUgc3BlY2lmaWVkCiMjIyB0aGlzIHNhbWUgcGF0aCB3aWxsIGJlIHVzZWQgaW5zaWRlIGFuZCBvdXRzaWRlIG9mIHRoZSBjb250YWluZXIKIyMjCkJBU0hfRlJBTUVXT1JLX0xPR19GSUxFPSR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEU6LSR7RlJBTUVXT1JLX1JPT1RfRElSfS9sb2dzL2Jhc2gubG9nfQoKIyMjCiMjIyBMT0cgTGV2ZWwKIyMjIG1pbmltdW0gbGV2ZWwgb2YgdGhlIG1lc3NhZ2VzIHRoYXQgd2lsbCBiZSBsb2dnZWQgaW50byBMT0dfRklMRQojIyMKIyMjIDA6IE5PIExPRwojIyMgMTogRVJST1IKIyMjIDI6IFdBUk5JTkcKIyMjIDM6IElORk8KIyMjIDQ6IERFQlVHCiMjIwpCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUw9JHtCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUw6LTB9CgojIGFic29sdXRlIGRpcmVjdG9yeSBjb250YWluaW5nIGRiIGltcG9ydCBzcWwgZHVtcHMKREJfSU1QT1JUX0RVTVBfRElSPSR7REJfSU1QT1JUX0RVTVBfRElSOi0ke0hPTUV9Ly5iYXNoLXRvb2xzL2RiSW1wb3J0RHVtcHN9CgojIGdhcmJhZ2UgY29sbGVjdCBhbGwgZmlsZXMgZm9yIHdoaWNoIG1vZGlmaWNhdGlvbiBpcyBncmVhdGVyIHRoYW4gZWc6IDMwIGRheXMgKCszMCkKIyBlYWNoIHRpbWUgYW4gZXhpc3RpbmcgZmlsZSBpcyB1c2VkIGJ5IGRiSW1wb3J0L2RiSW1wb3J0VGFibGUKIyB0aGUgZmlsZSBtb2RpZmljYXRpb24gdGltZSBpcyBzZXQgdG8gbm93CkRCX0lNUE9SVF9HQVJCQUdFX0NPTExFQ1RfREFZUz0ke0RCX0lNUE9SVF9HQVJCQUdFX0NPTExFQ1RfREFZUzotKzMwfQoKIyBhYnNvbHV0ZSBkaXJlY3RvcnkgY29udGFpbmluZyBkYlNjcmlwdHMgdXNlZCBieSBkYlNjcmlwdEFsbERhdGFiYXNlcwpTQ1JJUFRTX0ZPTERFUj0ke1NDUklQVFNfRk9MREVSOi0ke0hPTUV9Ly5iYXNoLXRvb2xzL2NvbmYvZGJTY3JpcHRzfQoKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIEFXUyBQYXJhbWV0ZXJzCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KUzNfQkFTRV9VUkw9JHtTM19CQVNFX1VSTDotfQoKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIFBvc3RtYW4gUGFyYW1ldGVycwojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tClBPU1RNQU5fQVBJX0tFWT0K" \ + "755" - # generate authFile for easy authentication - instanceNewInstance['AUTH_FILE']=$(mktemp -p "${TMPDIR:-/tmp}" -t "mysql.XXXXXXXXXXXX") - ( - echo "[client]" - echo "user = ${USER}" - echo "password = ${PASSWORD}" - echo "host = ${HOSTNAME}" - echo "port = ${PORT}" - ) >"${instanceNewInstance['AUTH_FILE']}" +declare -gx embed_file_bashToolsDefaultConfigTemplate="${PERSISTENT_TMPDIR:-/tmp}/db87222729e0f1ed9c597a486a61a08c/bashToolsDefaultConfigTemplate" - # some of those values can be overridden using the dsn file - # SKIP_COLUMN_NAMES enabled by default - instanceNewInstance['SKIP_COLUMN_NAMES']="${SKIP_COLUMN_NAMES:-1}" - instanceNewInstance['SSL_OPTIONS']="${MYSQL_SSL_OPTIONS:---ssl-mode=DISABLED}" - instanceNewInstance['QUERY_OPTIONS']="${MYSQL_QUERY_OPTIONS:---batch --raw --default-character-set=utf8}" - instanceNewInstance['DUMP_OPTIONS']="${MYSQL_DUMP_OPTIONS:---default-character-set=utf8 --compress --hex-blob --routines --triggers --single-transaction --set-gtid-purged=OFF --column-statistics=0 ${instanceNewInstance['SSL_OPTIONS']}}" - instanceNewInstance['DB_IMPORT_OPTIONS']="${DB_IMPORT_OPTIONS:---connect-timeout=5 --batch --raw --default-character-set=utf8}" + BASH_TOOLS_ROOT_DIR="$(cd "${CURRENT_DIR}/${RELATIVE_FRAMEWORK_DIR_TO_CURRENT_DIR}" && pwd -P)" + if [[ -d "${BASH_TOOLS_ROOT_DIR}/vendor/bash-tools-framework/" ]]; then + FRAMEWORK_ROOT_DIR="$(cd "${BASH_TOOLS_ROOT_DIR}/vendor/bash-tools-framework" && pwd -P)" + else + # if the directory does not exist yet, give a value to FRAMEWORK_ROOT_DIR + FRAMEWORK_ROOT_DIR="${BASH_TOOLS_ROOT_DIR}/vendor/bash-tools-framework" + fi + # shellcheck disable=SC2034 + FRAMEWORK_SRC_DIR="${FRAMEWORK_ROOT_DIR}/src" + # shellcheck disable=SC2034 + FRAMEWORK_BIN_DIR="${FRAMEWORK_ROOT_DIR}/bin" + # shellcheck disable=SC2034 + FRAMEWORK_VENDOR_DIR="${FRAMEWORK_ROOT_DIR}/vendor" + # shellcheck disable=SC2034 + FRAMEWORK_VENDOR_BIN_DIR="${FRAMEWORK_ROOT_DIR}/vendor/bin" - instanceNewInstance['INITIALIZED']=1 + if [[ -f "${HOME}/.bash-tools/.env" ]]; then + # shellcheck disable=SC2034 + BASH_FRAMEWORK_ENV_FILES=("${HOME}/.bash-tools/.env") + fi + + local envFile="${HOME}/.bash-tools/.env" + if [[ ! -f "${envFile}" ]]; then + mkdir -p "${HOME}/.bash-tools" + ( + echo "#!/usr/bin/env bash" + # shellcheck disable=SC2154 + echo "${embed_file_bashToolsDefaultConfigTemplate}" + ) >"${envFile}" + Log::displayInfo "Configuration file '${envFile}' created" + else + if ! grep -q '^POSTMAN_API_KEY=' "${envFile}"; then + ( + echo '# -----------------------------------------------------' + echo '# Postman Parameters' + echo '# -----------------------------------------------------' + echo 'POSTMAN_API_KEY=' + ) >>"${envFile}" + fi + fi + # shellcheck source=/conf/.env + source "${envFile}" || { + Log::displayError "impossible to load '${envFile}'" + exit 1 + } } -# @description set the general options to use on mysql command to query the database -# Differs than setOptions in the way that these options could change each time + +# @description convert base64 encoded back to target file +# if target file is executable prepend dir of target +# file to PATH to make binary available everywhere +# it is advised to include in the path of the target file +# the md5sum of the binFile # -# @arg $1 instanceSetQueryOptions:&Map (passed by reference) database instance to use -# @arg $2 optionsList:String query options list -Database::setQueryOptions() { - local -n instanceSetQueryOptions=$1 - # shellcheck disable=SC2034 - instanceSetQueryOptions['QUERY_OPTIONS']="$2" +# @arg $1 targetFile:String the file to write +# @arg $2 binFileBase64:String the base64 encoded file +# @arg $3 fileMode:String the chmod to set on the file +# @set PATH String prepend target embedded file binary directory to PATH variable if binary executable +Compiler::Embed::extractFileFromBase64() { + local targetFile="$1" + local binFileBase64="$2" + local fileMode="${3:-+x}" + local targetDir="${targetFile%/*}" + + if [[ ! -f "${targetFile}" ]]; then + if [[ ! -d "${targetDir}" ]]; then + mkdir -p "${targetDir}" + fi + base64 -d >"${targetFile}" <<<"${binFileBase64}" + chmod "${fileMode}" "${targetFile}" + fi + + if [[ -x "${targetFile}" ]]; then + Env::pathPrepend "${targetDir}" + fi } + # @description get absolute conf file from specified conf folder deduced using these rules # * from absolute file (ignores and ) # * relative to where script is executed (ignores and ) @@ -307,72 +500,310 @@ Conf::getAbsoluteFile() { return 1 } -# @description by default we skip the column names -# but sometimes we need column names to display some results -# disable this option temporarily and then restore it to true -# -# @arg $1 instanceSetQueryOptions:&Map (passed by reference) database instance to use -# @arg $2 skipColumnNames:Boolean 0 to disable, 1 to enable (hide column names) -Database::skipColumnNames() { - local -n instanceSkipColumnNames=$1 - # shellcheck disable=SC2034 - instanceSkipColumnNames['SKIP_COLUMN_NAMES']="$2" -} -# @description mysql query on a given db -# @warning could use QUERY_OPTIONS variable from dsn if defined -# @example -# cat file.sql | Database::query ... -# @arg $1 instanceQuery:&Map (passed by reference) database instance to use -# @arg $2 sqlQuery:String (optional) sql query or sql file to execute. if not provided or empty, the command can be piped -# @arg $3 dbName:String (optional) the db name +# @description check if dsn file has all the mandatory variables set +# Mandatory variables are: HOSTNAME, USER, PASSWORD, PORT # -# @exitcode mysql command status code -Database::query() { - local -n instanceQuery=$1 - local -a mysqlCommand=() - local -a queryOptions - - mysqlCommand+=(mysql) - mysqlCommand+=("--defaults-extra-file=${instanceQuery['AUTH_FILE']}") - IFS=' ' read -r -a queryOptions <<<"${instanceQuery['QUERY_OPTIONS']}" - mysqlCommand+=("${queryOptions[@]}") - if [[ "${instanceQuery['SKIP_COLUMN_NAMES']}" = "1" ]]; then - mysqlCommand+=("-s" "--skip-column-names") - fi - # add optional db name - if [[ -n "${3+x}" ]]; then - mysqlCommand+=("$3") - fi - # add optional sql query - if [[ -n "${2+x}" && -n "$2" && ! -f "$2" ]]; then - mysqlCommand+=("-e") - mysqlCommand+=("$2") - fi - Log::displayDebug "$(printf "execute command: '%s'" "${mysqlCommand[*]}")" - - if [[ -f "$2" ]]; then - "${mysqlCommand[@]}" <"$2" - else - "${mysqlCommand[@]}" +# @arg $1 dsnFileName:String dsn absolute filename +# @set HOSTNAME loaded from dsn file +# @set PORT loaded from dsn file +# @set USER loaded from dsn file +# @set PASSWORD loaded from dsn file +# @exitcode 0 on valid file +# @exitcode 1 if one of the properties of the conf file is invalid or if file not found +# @stderr log output if error found in conf file +Database::checkDsnFile() { + local dsnFileName="$1" + if [[ ! -f "${dsnFileName}" ]]; then + Log::displayError "dsn file ${dsnFileName} not found" + return 1 fi -} -# @description ensure COMMAND_BIN_DIR env var is set -# and PATH correctly prepared -# @noargs -# @set COMMAND_BIN_DIR string the directory where to find this command -# @set PATH string add directory where to find this command binary -Compiler::Facade::requireCommandBinDir() { - COMMAND_BIN_DIR="${CURRENT_DIR}" - Env::pathPrepend "${COMMAND_BIN_DIR}" -} - -declare -g FIRST_LOG_DATE LOG_LAST_LOG_DATE LOG_LAST_LOG_DATE_INIT LOG_LAST_DURATION_STR -FIRST_LOG_DATE="${EPOCHREALTIME/[^0-9]/}" -LOG_LAST_LOG_DATE="${FIRST_LOG_DATE}" -LOG_LAST_LOG_DATE_INIT=1 -LOG_LAST_DURATION_STR="" + ( + unset HOSTNAME PORT PASSWORD USER + # shellcheck source=/src/Database/testsData/dsn_valid.env + source "${dsnFileName}" + if [[ -z ${HOSTNAME+x} ]]; then + Log::displayError "dsn file ${dsnFileName} : HOSTNAME not provided" + return 1 + fi + if [[ -z "${HOSTNAME}" ]]; then + Log::displayWarning "dsn file ${dsnFileName} : HOSTNAME value not provided" + fi + if [[ "${HOSTNAME}" = "localhost" ]]; then + Log::displayWarning "dsn file ${dsnFileName} : check that HOSTNAME should not be 127.0.0.1 instead of localhost" + fi + if [[ -z "${PORT+x}" ]]; then + Log::displayError "dsn file ${dsnFileName} : PORT not provided" + return 1 + fi + if ! [[ ${PORT} =~ ^[0-9]+$ ]]; then + Log::displayError "dsn file ${dsnFileName} : PORT invalid" + return 1 + fi + if [[ -z "${USER+x}" ]]; then + Log::displayError "dsn file ${dsnFileName} : USER not provided" + return 1 + fi + if [[ -z "${PASSWORD+x}" ]]; then + Log::displayError "dsn file ${dsnFileName} : PASSWORD not provided" + return 1 + fi + ) +} + + +# @description create a new db instance +# Returns immediately if the instance is already initialized +# +# @arg $1 instanceNewInstance:&Map (passed by reference) database instance to use +# @arg $2 dsn:String dsn profile - load the dsn.env profile deduced using rules defined in Conf::getAbsoluteFile +# +# @example +# declare -Agx dbInstance +# Database::newInstance dbInstance "default.local" +# +# @exitcode 1 if dns file not able to loaded +Database::newInstance() { + local -n instanceNewInstance=$1 + local dsn="$2" + local DSN_FILE + + if [[ -v instanceNewInstance['INITIALIZED'] && "${instanceNewInstance['INITIALIZED']:-0}" == "1" ]]; then + return + fi + + # final auth file generated from dns file + instanceNewInstance['AUTH_FILE']="" + instanceNewInstance['DSN_FILE']="" + + # check dsn file + DSN_FILE="$(Conf::getAbsoluteFile "dsn" "${dsn}" "env")" || return 1 + Database::checkDsnFile "${DSN_FILE}" || return 1 + instanceNewInstance['DSN_FILE']="${DSN_FILE}" + + # shellcheck source=/src/Database/testsData/dsn_valid.env + source "${instanceNewInstance['DSN_FILE']}" + + instanceNewInstance['USER']="${USER}" + instanceNewInstance['PASSWORD']="${PASSWORD}" + instanceNewInstance['HOSTNAME']="${HOSTNAME}" + instanceNewInstance['PORT']="${PORT}" + + # generate authFile for easy authentication + instanceNewInstance['AUTH_FILE']=$(mktemp -p "${TMPDIR:-/tmp}" -t "mysql.XXXXXXXXXXXX") + ( + echo "[client]" + echo "user = ${USER}" + echo "password = ${PASSWORD}" + echo "host = ${HOSTNAME}" + echo "port = ${PORT}" + ) >"${instanceNewInstance['AUTH_FILE']}" + + # some of those values can be overridden using the dsn file + # SKIP_COLUMN_NAMES enabled by default + instanceNewInstance['SKIP_COLUMN_NAMES']="${SKIP_COLUMN_NAMES:-1}" + instanceNewInstance['SSL_OPTIONS']="${MYSQL_SSL_OPTIONS:---ssl-mode=DISABLED}" + instanceNewInstance['QUERY_OPTIONS']="${MYSQL_QUERY_OPTIONS:---batch --raw --default-character-set=utf8}" + instanceNewInstance['DUMP_OPTIONS']="${MYSQL_DUMP_OPTIONS:---default-character-set=utf8 --compression-algorithms --hex-blob --routines --triggers --single-transaction --set-gtid-purged=OFF --column-statistics=0 ${instanceNewInstance['SSL_OPTIONS']}}" + instanceNewInstance['DB_IMPORT_OPTIONS']="${DB_IMPORT_OPTIONS:---connect-timeout=5 --batch --raw --default-character-set=utf8}" + + instanceNewInstance['INITIALIZED']=1 +} + + +# @description mysql query on a given db +# @warning could use QUERY_OPTIONS variable from dsn if defined +# @example +# cat file.sql | Database::query ... +# @arg $1 instanceQuery:&Map (passed by reference) database instance to use +# @arg $2 sqlQuery:String (optional) sql query or sql file to execute. if not provided or empty, the command can be piped +# @arg $3 dbName:String (optional) the db name +# +# @exitcode mysql command status code +Database::query() { + local -n instanceQuery=$1 + local -a mysqlCommand=() + local -a queryOptions + + mysqlCommand+=(mysql) + mysqlCommand+=("--defaults-extra-file=${instanceQuery['AUTH_FILE']}") + IFS=' ' read -r -a queryOptions <<<"${instanceQuery['QUERY_OPTIONS']}" + mysqlCommand+=("${queryOptions[@]}") + if [[ "${instanceQuery['SKIP_COLUMN_NAMES']}" = "1" ]]; then + mysqlCommand+=("-s" "--skip-column-names") + fi + # add optional db name + if [[ -n "${3+x}" ]]; then + mysqlCommand+=("$3") + fi + # add optional sql query + if [[ -n "${2+x}" && -n "$2" && ! -f "$2" ]]; then + mysqlCommand+=("-e") + mysqlCommand+=("$2") + fi + Log::displayDebug "$(printf "execute command: '%s'" "${mysqlCommand[*]}")" + + if [[ -f "$2" ]]; then + "${mysqlCommand[@]}" <"$2" + else + "${mysqlCommand[@]}" + fi +} + + +# @description set the general options to use on mysql command to query the database +# Differs than setOptions in the way that these options could change each time +# +# @arg $1 instanceSetQueryOptions:&Map (passed by reference) database instance to use +# @arg $2 optionsList:String query options list +Database::setQueryOptions() { + local -n instanceSetQueryOptions=$1 + # shellcheck disable=SC2034 + instanceSetQueryOptions['QUERY_OPTIONS']="$2" +} + + +# @description by default we skip the column names +# but sometimes we need column names to display some results +# disable this option temporarily and then restore it to true +# +# @arg $1 instanceSetQueryOptions:&Map (passed by reference) database instance to use +# @arg $2 skipColumnNames:Boolean 0 to disable, 1 to enable (hide column names) +Database::skipColumnNames() { + local -n instanceSkipColumnNames=$1 + # shellcheck disable=SC2034 + instanceSkipColumnNames['SKIP_COLUMN_NAMES']="$2" +} + + +# @description check if all requirements are satisfied +# to execute dbImport commands +Db::checkRequirements() { + if [[ "${SKIP_REQUIREMENTS_CHECKS:-0}" = "1" ]]; then + return 0 + fi + local -i failures=0 + echo + Assert::commandExists mysql "sudo apt-get install -y mysql-client" || ((++failures)) + Assert::commandExists mysqlshow "sudo apt-get install -y mysql-client" || ((++failures)) + Assert::commandExists mysqldump "sudo apt-get install -y mysql-client" || ((++failures)) + Assert::commandExists pv "sudo apt-get install -y pv" || ((++failures)) + Assert::commandExists gawk "sudo apt-get install -y gawk" || ((++failures)) + Assert::commandExists awk "sudo apt-get install -y gawk" || ((++failures)) + Version::checkMinimal "gawk" "--version" "5.0.1" || ((++failures)) + return "${failures}" +} + + +# @description prepend directories to the PATH environment variable +# @arg $@ args:String[] list of directories to prepend +# @set PATH update PATH with the directories prepended +Env::pathPrepend() { + local arg + for arg in "$@"; do + if [[ -d "${arg}" && ":${PATH}:" != *":${arg}:"* ]]; then + PATH="$(realpath "${arg}"):${PATH}" + fi + done +} + + +# @description ensure env files are loaded +# @arg $@ list of default files to load at the end +# @exitcode 1 if one of env files fails to load +# @stderr diagnostics information is displayed +# shellcheck disable=SC2120 +Env::requireLoad() { + export REQUIRE_FUNCTION_ENV_REQUIRE_LOAD_LOADED=1 + + local -a defaultFiles=("$@") + # get list of possible config files + local -a configFiles=() + if [[ -n "${BASH_FRAMEWORK_ENV_FILES[0]+1}" ]]; then + # BASH_FRAMEWORK_ENV_FILES is an array + configFiles+=("${BASH_FRAMEWORK_ENV_FILES[@]}") + fi + local localFrameworkConfigFile + localFrameworkConfigFile="$(pwd)/.framework-config" + if [[ -f "${localFrameworkConfigFile}" ]]; then + configFiles+=("${localFrameworkConfigFile}") + fi + if [[ -f "${FRAMEWORK_ROOT_DIR}/.framework-config" ]]; then + configFiles+=("${FRAMEWORK_ROOT_DIR}/.framework-config") + fi + configFiles+=("${optionEnvFiles[@]}") + configFiles+=("${defaultFiles[@]}") + + for file in "${configFiles[@]}"; do + # shellcheck source=/.framework-config + CURRENT_LOADED_ENV_FILE="${file}" source "${file}" || { + Log::displayError "while loading config file: ${file}" + return 1 + } + done +} + + +# @description concatenate 2 paths and ensure the path is correct using realpath -m +# @arg $1 basePath:String +# @arg $2 subPath:String +File::concatenatePath() { + + if [[ "${REQUIRE_FUNCTION_LINUX_REQUIRE_REALPATH_COMMAND_LOADED:-0}" != 1 ]]; then + echo >&2 "Requirement Linux::requireRealpathCommand has not been loaded" + exit 1 + fi + + local basePath="$1" + local subPath="$2" + local fullPath="${basePath:+${basePath}/}${subPath}" + + realpath -m "${fullPath}" 2>/dev/null +} + + +# @description create a temp file using default TMPDIR variable +# @env TMPDIR String (default value /tmp) +# @arg $1 templateName:String template name to use(optional) +Framework::createTempFile() { + mktemp -p "${TMPDIR:-/tmp}" -t "${1:-}.XXXXXXXXXXXX" +} + + +# @description ensure running user is not root +# @exitcode 1 if current user is root +# @stderr diagnostics information is displayed +Linux::requireExecutedAsUser() { + if [[ "$(id -u)" = "0" ]]; then + Log::fatal "this script should be executed as normal user" + fi +} + + +# @description ensure command realpath is available +# @exitcode 1 if realpath command not available +# @stderr diagnostics information is displayed +Linux::requireRealpathCommand() { + export REQUIRE_FUNCTION_LINUX_REQUIRE_REALPATH_COMMAND_LOADED=1 + + Assert::commandExists realpath +} + + +# @description ensure command tar is available +# @exitcode 1 if tar command not available +# @stderr diagnostics information is displayed +Linux::requireTarCommand() { + Assert::commandExists tar +} + + +declare -g FIRST_LOG_DATE LOG_LAST_LOG_DATE LOG_LAST_LOG_DATE_INIT LOG_LAST_DURATION_STR +FIRST_LOG_DATE="${EPOCHREALTIME/[^0-9]/}" +LOG_LAST_LOG_DATE="${FIRST_LOG_DATE}" +LOG_LAST_LOG_DATE_INIT=1 +LOG_LAST_DURATION_STR="" # @description compute duration since last call to this function # the result is set in following env variables. @@ -406,115 +837,106 @@ Log::computeDuration() { fi } -# @description log message to file + +# @description Display message using debug color (gray) # @arg $1 message:String the message to display -Log::logInfo() { - if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_INFO)); then - Log::logMessage "${2:-INFO}" "$1" +# @env DISPLAY_DURATION int (default 0) if 1 display elapsed time information between 2 info logs +# @env LOG_CONTEXT String allows to contextualize the log +Log::displayDebug() { + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_DEBUG)); then + Log::computeDuration + echo -e "${__DEBUG_COLOR}DEBUG - ${LOG_CONTEXT:-}${LOG_LAST_DURATION_STR:-}${1}${__RESET_COLOR}" >&2 fi + Log::logDebug "$1" } -# @description log message to file + +# @description Display message using error color (red) # @arg $1 message:String the message to display -Log::logDebug() { - if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_DEBUG)); then - Log::logMessage "${2:-DEBUG}" "$1" +# @env DISPLAY_DURATION int (default 0) if 1 display elapsed time information between 2 info logs +# @env LOG_CONTEXT String allows to contextualize the log +Log::displayError() { + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_ERROR)); then + Log::computeDuration + echo -e "${__ERROR_COLOR}ERROR - ${LOG_CONTEXT:-}${LOG_LAST_DURATION_STR:-}${1}${__RESET_COLOR}" >&2 fi + Log::logError "$1" } -# @description log message to file + +# @description Display message using info color (bg light blue/fg white) # @arg $1 message:String the message to display -Log::logFatal() { - Log::logMessage "${2:-FATAL}" "$1" +# @env DISPLAY_DURATION int (default 0) if 1 display elapsed time information between 2 info logs +# @env LOG_CONTEXT String allows to contextualize the log +Log::displayInfo() { + local type="${2:-INFO}" + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_INFO)); then + Log::computeDuration + echo -e "${__INFO_COLOR}${type} - ${LOG_CONTEXT:-}${LOG_LAST_DURATION_STR:-}${1}${__RESET_COLOR}" >&2 + fi + Log::logInfo "$1" "${type}" } -# @description check if dsn file has all the mandatory variables set -# Mandatory variables are: HOSTNAME, USER, PASSWORD, PORT -# -# @arg $1 dsnFileName:String dsn absolute filename -# @set HOSTNAME loaded from dsn file -# @set PORT loaded from dsn file -# @set USER loaded from dsn file -# @set PASSWORD loaded from dsn file -# @exitcode 0 on valid file -# @exitcode 1 if one of the properties of the conf file is invalid or if file not found -# @stderr log output if error found in conf file -Database::checkDsnFile() { - local dsnFileName="$1" - if [[ ! -f "${dsnFileName}" ]]; then - Log::displayError "dsn file ${dsnFileName} not found" - return 1 + +# @description Display message using warning color (yellow) +# @arg $1 message:String the message to display +# @env DISPLAY_DURATION int (default 0) if 1 display elapsed time information between 2 info logs +# @env LOG_CONTEXT String allows to contextualize the log +Log::displayWarning() { + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_WARNING)); then + Log::computeDuration + echo -e "${__WARNING_COLOR}WARN - ${LOG_CONTEXT:-}${LOG_LAST_DURATION_STR:-}${1}${__RESET_COLOR}" >&2 fi + Log::logWarning "$1" +} - ( - unset HOSTNAME PORT PASSWORD USER - # shellcheck source=/src/Database/testsData/dsn_valid.env - source "${dsnFileName}" - if [[ -z ${HOSTNAME+x} ]]; then - Log::displayError "dsn file ${dsnFileName} : HOSTNAME not provided" - return 1 - fi - if [[ -z "${HOSTNAME}" ]]; then - Log::displayWarning "dsn file ${dsnFileName} : HOSTNAME value not provided" - fi - if [[ "${HOSTNAME}" = "localhost" ]]; then - Log::displayWarning "dsn file ${dsnFileName} : check that HOSTNAME should not be 127.0.0.1 instead of localhost" - fi - if [[ -z "${PORT+x}" ]]; then - Log::displayError "dsn file ${dsnFileName} : PORT not provided" - return 1 - fi - if ! [[ ${PORT} =~ ^[0-9]+$ ]]; then - Log::displayError "dsn file ${dsnFileName} : PORT invalid" - return 1 - fi - if [[ -z "${USER+x}" ]]; then - Log::displayError "dsn file ${dsnFileName} : USER not provided" - return 1 - fi - if [[ -z "${PASSWORD+x}" ]]; then - Log::displayError "dsn file ${dsnFileName} : PASSWORD not provided" - return 1 - fi - ) + +# @description Display message using error color (red) and exit immediately with error status 1 +# @arg $1 message:String the message to display +# @env DISPLAY_DURATION int (default 0) if 1 display elapsed time information between 2 info logs +# @env LOG_CONTEXT String allows to contextualize the log +Log::fatal() { + Log::computeDuration + echo -e "${__ERROR_COLOR}FATAL - ${LOG_CONTEXT:-}${LOG_LAST_DURATION_STR:-}${1}${__RESET_COLOR}" >&2 + Log::logFatal "$1" + exit 1 } -# @description concatenate 2 paths and ensure the path is correct using realpath -m -# @arg $1 basePath:String -# @arg $2 subPath:String -# @require Linux::requireRealpathCommand -File::concatenatePath() { - local basePath="$1" - local subPath="$2" - local fullPath="${basePath:+${basePath}/}${subPath}" - realpath -m "${fullPath}" 2>/dev/null +# @description log message to file +# @arg $1 message:String the message to display +Log::logDebug() { + if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_DEBUG)); then + Log::logMessage "${2:-DEBUG}" "$1" + fi } -# @description Display message using error color (red) + +# @description log message to file # @arg $1 message:String the message to display -# @env DISPLAY_DURATION int (default 0) if 1 display elapsed time information between 2 info logs -# @env LOG_CONTEXT String allows to contextualize the log -Log::displayError() { - if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_ERROR)); then - Log::computeDuration - echo -e "${__ERROR_COLOR}ERROR - ${LOG_CONTEXT:-}${LOG_LAST_DURATION_STR:-}${1}${__RESET_COLOR}" >&2 +Log::logError() { + if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_ERROR)); then + Log::logMessage "${2:-ERROR}" "$1" fi - Log::logError "$1" } -# @description prepend directories to the PATH environment variable -# @arg $@ args:String[] list of directories to prepend -# @set PATH update PATH with the directories prepended -Env::pathPrepend() { - local arg - for arg in "$@"; do - if [[ -d "${arg}" && ":${PATH}:" != *":${arg}:"* ]]; then - PATH="$(realpath "${arg}"):${PATH}" - fi - done + +# @description log message to file +# @arg $1 message:String the message to display +Log::logFatal() { + Log::logMessage "${2:-FATAL}" "$1" +} + + +# @description log message to file +# @arg $1 message:String the message to display +Log::logInfo() { + if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_INFO)); then + Log::logMessage "${2:-INFO}" "$1" + fi } + # @description Internal: common log message # @example text # [date]|[levelMsg]|message @@ -528,9 +950,18 @@ Env::pathPrepend() { # @env BASH_FRAMEWORK_LOG_FILE String log file to use, do nothing if empty # @env BASH_FRAMEWORK_LOG_LEVEL int log level log only if > OFF or fatal messages # @stderr diagnostics information is displayed -# @require Env::requireLoad -# @require Log::requireLoad Log::logMessage() { + + if [[ "${REQUIRE_FUNCTION_ENV_REQUIRE_LOAD_LOADED:-0}" != 1 ]]; then + echo >&2 "Requirement Env::requireLoad has not been loaded" + exit 1 + fi + + if [[ "${REQUIRE_FUNCTION_LOG_REQUIRE_LOAD_LOADED:-0}" != 1 ]]; then + echo >&2 "Requirement Log::requireLoad has not been loaded" + exit 1 + fi + local levelMsg="$1" local msg="$2" local date @@ -542,32 +973,6 @@ Log::logMessage() { fi } -# @description Display message using warning color (yellow) -# @arg $1 message:String the message to display -# @env DISPLAY_DURATION int (default 0) if 1 display elapsed time information between 2 info logs -# @env LOG_CONTEXT String allows to contextualize the log -Log::displayWarning() { - if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_WARNING)); then - Log::computeDuration - echo -e "${__WARNING_COLOR}WARN - ${LOG_CONTEXT:-}${LOG_LAST_DURATION_STR:-}${1}${__RESET_COLOR}" >&2 - fi - Log::logWarning "$1" -} - -# @description log message to file -# @arg $1 message:String the message to display -Log::logError() { - if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_ERROR)); then - Log::logMessage "${2:-ERROR}" "$1" - fi -} - -# @description ensure command realpath is available -# @exitcode 1 if realpath command not available -# @stderr diagnostics information is displayed -Linux::requireRealpathCommand() { - Assert::commandExists realpath -} # @description log message to file # @arg $1 message:String the message to display @@ -577,60 +982,6 @@ Log::logWarning() { fi } -# @description check if command specified exists or return 1 -# with error and message if not -# -# @arg $1 commandName:String on which existence must be checked -# @arg $2 helpIfNotExists:String a help command to display if the command does not exist -# -# @exitcode 1 if the command specified does not exist -# @stderr diagnostic information + help if second argument is provided -Assert::commandExists() { - local commandName="$1" - local helpIfNotExists="$2" - - "${BASH_FRAMEWORK_COMMAND:-command}" -v "${commandName}" >/dev/null 2>/dev/null || { - Log::displayError "${commandName} is not installed, please install it" - if [[ -n "${helpIfNotExists}" ]]; then - Log::displayInfo "${helpIfNotExists}" - fi - return 1 - } - return 0 -} - -# @description ensure env files are loaded -# @arg $@ list of default files to load at the end -# @exitcode 1 if one of env files fails to load -# @stderr diagnostics information is displayed -# shellcheck disable=SC2120 -Env::requireLoad() { - local -a defaultFiles=("$@") - # get list of possible config files - local -a configFiles=() - if [[ -n "${BASH_FRAMEWORK_ENV_FILES[0]+1}" ]]; then - # BASH_FRAMEWORK_ENV_FILES is an array - configFiles+=("${BASH_FRAMEWORK_ENV_FILES[@]}") - fi - local localFrameworkConfigFile - localFrameworkConfigFile="$(pwd)/.framework-config" - if [[ -f "${localFrameworkConfigFile}" ]]; then - configFiles+=("${localFrameworkConfigFile}") - fi - if [[ -f "${FRAMEWORK_ROOT_DIR}/.framework-config" ]]; then - configFiles+=("${FRAMEWORK_ROOT_DIR}/.framework-config") - fi - configFiles+=("${optionEnvFiles[@]}") - configFiles+=("${defaultFiles[@]}") - - for file in "${configFiles[@]}"; do - # shellcheck source=/.framework-config - CURRENT_LOADED_ENV_FILE="${file}" source "${file}" || { - Log::displayError "while loading config file: ${file}" - return 1 - } - done -} # @description activate or not Log::display* and Log::log* functions # based on BASH_FRAMEWORK_DISPLAY_LEVEL and BASH_FRAMEWORK_LOG_LEVEL @@ -644,9 +995,20 @@ Env::requireLoad() { # @env BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION int do log rotation if > 0 # @exitcode 0 always successful # @stderr diagnostics information about log file is displayed -# @require Env::requireLoad -# @require UI::requireTheme Log::requireLoad() { + export REQUIRE_FUNCTION_LOG_REQUIRE_LOAD_LOADED=1 + + + if [[ "${REQUIRE_FUNCTION_ENV_REQUIRE_LOAD_LOADED:-0}" != 1 ]]; then + echo >&2 "Requirement Env::requireLoad has not been loaded" + exit 1 + fi + + if [[ "${REQUIRE_FUNCTION_UI_REQUIRE_THEME_LOADED:-0}" != 1 ]]; then + echo >&2 "Requirement UI::requireTheme has not been loaded" + exit 1 + fi + if [[ -z "${BASH_FRAMEWORK_LOG_FILE:-}" ]]; then BASH_FRAMEWORK_LOG_LEVEL=${__LEVEL_OFF} export BASH_FRAMEWORK_LOG_LEVEL @@ -678,6 +1040,7 @@ Log::requireLoad() { fi } + # @description To be called before logging in the log file # @arg $1 file:string log file name # @arg $2 maxLogFilesCount:int maximum number of log files @@ -700,17 +1063,37 @@ Log::rotate() { fi } + +# @description draw a line with the character passed in parameter repeated depending on terminal width +# @arg $1 character:String character to use as separator (default value #) +UI::drawLine() { + local character="${1:-#}" + local -i width=${COLUMNS:-0} + if ((width == 0)) && [[ -t 1 ]]; then + width=$(tput cols) + fi + if ((width == 0)); then + width=80 + fi + printf -- "${character}%.0s" $(seq "${COLUMNS:-$([[ -t 1 ]] && tput cols || echo '80')}") + echo +} + + # @description load color theme # @noargs # @env BASH_FRAMEWORK_THEME String theme to use # @env LOAD_THEME int 0 to avoid loading theme # @exitcode 0 always successful UI::requireTheme() { + export REQUIRE_FUNCTION_UI_REQUIRE_THEME_LOADED=1 + if [[ "${LOAD_THEME:-1}" = "1" ]]; then UI::theme "${BASH_FRAMEWORK_THEME-default}" fi } + # @description load colors theme constants # @warning if tty not opened, noColor theme will be chosen # @arg $1 theme:String the theme to use (default, noColor) @@ -738,86 +1121,427 @@ UI::theme() { if [[ ! "${theme}" =~ -force$ ]] && ! Assert::tty; then theme="noColor" fi - case "${theme}" in - default | default-force) - theme="default" + case "${theme}" in + default | default-force) + theme="default" + ;; + noColor) ;; + *) + Log::fatal "invalid theme provided" + ;; + esac + if [[ "${theme}" = "default" ]]; then + BASH_FRAMEWORK_THEME="default" + # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting + __ERROR_COLOR='\e[31m' # Red + __INFO_COLOR='\e[44m' # white on lightBlue + __SUCCESS_COLOR='\e[32m' # Green + __WARNING_COLOR='\e[33m' # Yellow + __SKIPPED_COLOR='\e[33m' # Yellow + __DEBUG_COLOR='\e[37m' # Gray + __HELP_COLOR='\e[7;49;33m' # Black on Gold + __TEST_COLOR='\e[100m' # Light magenta + __TEST_ERROR_COLOR='\e[41m' # white on red + __HELP_TITLE_COLOR="\e[1;37m" # Bold + __HELP_OPTION_COLOR="\e[1;34m" # Blue + # Internal: reset color + __RESET_COLOR='\e[0m' # Reset Color + # shellcheck disable=SC2155,SC2034 + __HELP_EXAMPLE="$(echo -e "\e[2;97m")" + # shellcheck disable=SC2155,SC2034 + __HELP_TITLE="$(echo -e "\e[1;37m")" + # shellcheck disable=SC2155,SC2034 + __HELP_NORMAL="$(echo -e "\033[0m")" + else + BASH_FRAMEWORK_THEME="noColor" + # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting + __ERROR_COLOR='' + __INFO_COLOR='' + __SUCCESS_COLOR='' + __WARNING_COLOR='' + __SKIPPED_COLOR='' + __DEBUG_COLOR='' + __HELP_COLOR='' + __TEST_COLOR='' + __TEST_ERROR_COLOR='' + __HELP_TITLE_COLOR='' + __HELP_OPTION_COLOR='' + # Internal: reset color + __RESET_COLOR='' + __HELP_EXAMPLE='' + __HELP_TITLE='' + __HELP_NORMAL='' + fi +} + + +# @description Check that command version is greater than expected minimal version +# display warning if command version greater than expected minimal version +# display error if command version less than expected minimal version and exit 1 +# @arg $1 commandName:String command path +# @arg $2 argVersion:String command line parameters to launch to get command version +# @arg $3 minimalVersion:String expected minimal command version +# @arg $4 parseVersionCallback:Function +# @arg $5 help:String optional help message to display if command does not exist +# @exitcode 0 if command version greater or equal to expected minimal version +# @exitcode 1 if command version less than expected minimal version +# @exitcode 2 if command does not exist +Version::checkMinimal() { + local commandName="$1" + local argVersion="$2" + local minimalVersion="$3" + local parseVersionCallback=${4:-Version::parse} + local help="${5:-}" + + Assert::commandExists "${commandName}" "${help}" || return 2 + + # shellcheck disable=SC2034 + local status=0 + # shellcheck disable=SC2034 + local -a pipeStatus=() + local version + version="$("${commandName}" "${argVersion}" 2>&1 | ${parseVersionCallback} || Bash::handlePipelineFailure status pipeStatus)" + + Log::displayDebug "check ${commandName} version ${version} against minimal ${minimalVersion}" + + Version::compare "${version}" "${minimalVersion}" || { + local result=$? + if [[ "${result}" = "1" ]]; then + Log::displayInfo "${commandName} version is ${version} greater than ${minimalVersion}" + elif [[ "${result}" = "2" ]]; then + Log::displayError "${commandName} minimal version is ${minimalVersion}, your version is ${version}" + return 1 + fi + return 0 + } + +} + + +# @description compare 2 version numbers +# @arg $1 version1:String version 1 +# @arg $2 version2:String version 2 +# @exitcode 0 if equal +# @exitcode 1 if version1 > version2 +# @exitcode 2 else +Version::compare() { + if [[ "$1" = "$2" ]]; then + return 0 + fi + local IFS=. + # shellcheck disable=2206 + local i ver1=($1) ver2=($2) + # fill empty fields in ver1 with zeros + for ((i = ${#ver1[@]}; i < ${#ver2[@]}; i++)); do + ver1[i]=0 + done + for ((i = 0; i < ${#ver1[@]}; i++)); do + if [[ -z "${ver2[i]+unset}" ]] || [[ -z ${ver2[i]} ]]; then + # fill empty fields in ver2 with zeros + ver2[i]=0 + fi + if ((10#${ver1[i]} > 10#${ver2[i]})); then + return 1 + fi + if ((10#${ver1[i]} < 10#${ver2[i]})); then + return 2 + fi + done + return 0 +} + + +# @description filter to keep only version number from a string +# @arg $@ files:String[] the files to filter +# @exitcode * if one of the filter command fails +# @stdin you can use stdin as alternative to files argument +# @stdout the filtered content +# shellcheck disable=SC2120 +Version::parse() { + # match anything, print(p), exit on first match(Q) + sed -En \ + -e 's/\x1b\[[0-9;]*[mGKHF]//g' \ + -e 's/[^0-9]*(([0-9]+\.)*[0-9]+).*/\1/' \ + -e '//{p;Q}' \ + "$@" +} +# FUNCTIONS + + +declare -a BASH_FRAMEWORK_ARGV_FILTERED=() + +beforeParseCallback() { + Env::requireLoad + UI::requireTheme + Log::requireLoad +} + +copyrightCallback() { + if [[ -z "${copyrightBeginYear}" ]]; then + copyrightBeginYear="$(date +%Y)" + fi + echo "Copyright (c) ${copyrightBeginYear}-now François Chastanet" +} + +# shellcheck disable=SC2317 # if function is overridden +updateArgListInfoVerboseCallback() { + BASH_FRAMEWORK_ARGV_FILTERED+=(--verbose) +} +# shellcheck disable=SC2317 # if function is overridden +updateArgListDebugVerboseCallback() { + BASH_FRAMEWORK_ARGV_FILTERED+=(-vv) +} +# shellcheck disable=SC2317 # if function is overridden +updateArgListTraceVerboseCallback() { + BASH_FRAMEWORK_ARGV_FILTERED+=(-vvv) +} +# shellcheck disable=SC2317 # if function is overridden +updateArgListEnvFileCallback() { :; } +# shellcheck disable=SC2317 # if function is overridden +updateArgListLogLevelCallback() { :; } +# shellcheck disable=SC2317 # if function is overridden +updateArgListDisplayLevelCallback() { :; } +# shellcheck disable=SC2317 # if function is overridden +updateArgListNoColorCallback() { + BASH_FRAMEWORK_ARGV_FILTERED+=(--no-color) +} +# shellcheck disable=SC2317 # if function is overridden +updateArgListThemeCallback() { :; } +# shellcheck disable=SC2317 # if function is overridden +updateArgListQuietCallback() { :; } + +# shellcheck disable=SC2317 # if function is overridden +optionHelpCallback() { + Log::displayError "optionHelpCallback needs to be overridden" + exit 0 +} + +# shellcheck disable=SC2317 # if function is overridden +optionVersionCallback() { + # shellcheck disable=SC2154 + echo "${SCRIPT_NAME} version ${versionNumber}" + exit 0 +} + +# shellcheck disable=SC2317 # if function is overridden +optionEnvFileCallback() { + local envFile="$2" + Log::displayWarning "Command ${SCRIPT_NAME} - Option --env-file is deprecated and will be removed in the future" + if [[ ! -f "${envFile}" || ! -r "${envFile}" ]]; then + Log::displayError "Command ${SCRIPT_NAME} - Option --env-file - File '${envFile}' doesn't exist" + exit 1 + fi +} + +# shellcheck disable=SC2317 # if function is overridden +optionInfoVerboseCallback() { + BASH_FRAMEWORK_ARGS_VERBOSE_OPTION='--verbose' + BASH_FRAMEWORK_ARGS_VERBOSE=${__VERBOSE_LEVEL_INFO} + echo "BASH_FRAMEWORK_DISPLAY_LEVEL=${__LEVEL_INFO}" >>"${overrideEnvFile}" +} + +# shellcheck disable=SC2317 # if function is overridden +optionDebugVerboseCallback() { + BASH_FRAMEWORK_ARGS_VERBOSE_OPTION='-vv' + BASH_FRAMEWORK_ARGS_VERBOSE=${__VERBOSE_LEVEL_DEBUG} + echo "BASH_FRAMEWORK_DISPLAY_LEVEL=${__LEVEL_DEBUG}" >>"${overrideEnvFile}" +} + +# shellcheck disable=SC2317 # if function is overridden +optionTraceVerboseCallback() { + # shellcheck disable=SC2034 + BASH_FRAMEWORK_ARGS_VERBOSE_OPTION='-vvv' + BASH_FRAMEWORK_ARGS_VERBOSE=${__VERBOSE_LEVEL_TRACE} + echo "BASH_FRAMEWORK_DISPLAY_LEVEL=${__LEVEL_DEBUG}" >>"${overrideEnvFile}" +} + +getLevel() { + local levelName="$1" + case "${levelName^^}" in + OFF) + echo "${__LEVEL_OFF}" + ;; + ERR | ERROR) + echo "${__LEVEL_ERROR}" + ;; + WARN | WARNING) + echo "${__LEVEL_WARNING}" + ;; + INFO) + echo "${__LEVEL_INFO}" + ;; + DEBUG | TRACE) + echo "${__LEVEL_DEBUG}" ;; - noColor) ;; *) - Log::fatal "invalid theme provided" + Log::displayError "Command ${SCRIPT_NAME} - Invalid level ${level}" + return 1 ;; esac - if [[ "${theme}" = "default" ]]; then - BASH_FRAMEWORK_THEME="default" - # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - __ERROR_COLOR='\e[31m' # Red - __INFO_COLOR='\e[44m' # white on lightBlue - __SUCCESS_COLOR='\e[32m' # Green - __WARNING_COLOR='\e[33m' # Yellow - __SKIPPED_COLOR='\e[33m' # Yellow - __DEBUG_COLOR='\e[37m' # Gray - __HELP_COLOR='\e[7;49;33m' # Black on Gold - __TEST_COLOR='\e[100m' # Light magenta - __TEST_ERROR_COLOR='\e[41m' # white on red - __HELP_TITLE_COLOR="\e[1;37m" # Bold - __HELP_OPTION_COLOR="\e[1;34m" # Blue - # Internal: reset color - __RESET_COLOR='\e[0m' # Reset Color - # shellcheck disable=SC2155,SC2034 - __HELP_EXAMPLE="$(echo -e "\e[2;97m")" - # shellcheck disable=SC2155,SC2034 - __HELP_TITLE="$(echo -e "\e[1;37m")" - # shellcheck disable=SC2155,SC2034 - __HELP_NORMAL="$(echo -e "\033[0m")" - else - BASH_FRAMEWORK_THEME="noColor" - # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - __ERROR_COLOR='' - __INFO_COLOR='' - __SUCCESS_COLOR='' - __WARNING_COLOR='' - __SKIPPED_COLOR='' - __DEBUG_COLOR='' - __HELP_COLOR='' - __TEST_COLOR='' - __TEST_ERROR_COLOR='' - __HELP_TITLE_COLOR='' - __HELP_OPTION_COLOR='' - # Internal: reset color - __RESET_COLOR='' - __HELP_EXAMPLE='' - __HELP_TITLE='' - __HELP_NORMAL='' +} + +getVerboseLevel() { + local levelName="$1" + case "${levelName^^}" in + OFF) + echo "${__VERBOSE_LEVEL_OFF}" + ;; + ERR | ERROR | WARN | WARNING | INFO) + echo "${__VERBOSE_LEVEL_INFO}" + ;; + DEBUG) + echo "${__VERBOSE_LEVEL_DEBUG}" + ;; + TRACE) + echo "${__VERBOSE_LEVEL_TRACE}" + ;; + *) + Log::displayError "Command ${SCRIPT_NAME} - Invalid level ${level}" + return 1 + ;; + esac +} + +# shellcheck disable=SC2317 # if function is overridden +optionDisplayLevelCallback() { + local level="$2" + local logLevel verboseLevel + logLevel="$(getLevel "${level}")" + verboseLevel="$(getVerboseLevel "${level}")" + BASH_FRAMEWORK_ARGS_VERBOSE=${verboseLevel} + echo "BASH_FRAMEWORK_DISPLAY_LEVEL=${logLevel}" >>"${overrideEnvFile}" +} + +# shellcheck disable=SC2317 # if function is overridden +optionLogLevelCallback() { + local level="$2" + local logLevel verboseLevel + logLevel="$(getLevel "${level}")" + verboseLevel="$(getVerboseLevel "${level}")" + # shellcheck disable=SC2034 + BASH_FRAMEWORK_ARGS_VERBOSE=${verboseLevel} + echo "BASH_FRAMEWORK_LOG_LEVEL=${logLevel}" >>"${overrideEnvFile}" +} + +# shellcheck disable=SC2317 # if function is overridden +optionLogFileCallback() { + local logFile="$2" + echo "BASH_FRAMEWORK_LOG_FILE='${logFile}'" >>"${overrideEnvFile}" +} + +# shellcheck disable=SC2317 # if function is overridden +optionQuietCallback() { + echo "BASH_FRAMEWORK_QUIET_MODE=1" >>"${overrideEnvFile}" +} + +# shellcheck disable=SC2317 # if function is overridden +optionNoColorCallback() { + UI::theme "noColor" +} + +# shellcheck disable=SC2317 # if function is overridden +optionThemeCallback() { + UI::theme "$2" +} + +displayConfig() { + echo "Config" + UI::drawLine "-" + local var + while read -r var; do + printf '%-40s = %s\n' "${var}" "$(declare -p "${var}" | sed -E -e 's/^[^=]+=(.*)/\1/')" + done < <(typeset -p | awk 'match($3, "^(BASH_FRAMEWORK_[^=]+)=", m) { print m[1] }' | sort) + exit 0 +} + +optionBashFrameworkConfigCallback() { + if [[ ! -f "$2" ]]; then + Log::fatal "Command ${SCRIPT_NAME} - Bash framework config file '$2' does not exists" fi } -# @description check if tty (interactive mode) is active -# @noargs -# @exitcode 1 if tty not active -# @env NON_INTERACTIVE if 1 consider as not interactive even if environment is interactive -# @env INTERACTIVE if 1 consider as interactive even if environment is not interactive -Assert::tty() { - if [[ "${NON_INTERACTIVE:-0}" = "1" ]]; then - return 1 +defaultFrameworkConfig="$( + cat <<'EOF' + +# copied from src/_includes/.framework-config.default +# shellcheck disable=SC2034 + +REAL_SCRIPT_FILE="${REAL_SCRIPT_FILE:-$(readlink -e "$(realpath "${BASH_SOURCE[0]}")")}" +FRAMEWORK_ROOT_DIR="${FRAMEWORK_ROOT_DIR:-${REAL_SCRIPT_FILE%/*/*}}" +FRAMEWORK_SRC_DIR="${FRAMEWORK_SRC_DIR:-${FRAMEWORK_ROOT_DIR}/src}" +FRAMEWORK_BIN_DIR="${FRAMEWORK_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/bin}" +FRAMEWORK_VENDOR_DIR="${FRAMEWORK_VENDOR_DIR:-${FRAMEWORK_ROOT_DIR}/vendor}" +FRAMEWORK_VENDOR_BIN_DIR="${FRAMEWORK_VENDOR_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/vendor/bin}" + +# describe the functions that will be skipped from being imported +FRAMEWORK_FUNCTIONS_IGNORE_REGEXP="${FRAMEWORK_FUNCTIONS_IGNORE_REGEXP:-^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|Acquire::ForceIPv4)$}" +# describe the files that do not contain function to be imported +NON_FRAMEWORK_FILES_REGEXP="${NON_FRAMEWORK_FILES_REGEXP:-(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/_binaries|^src/_includes|^src/batsHeaders.sh$|^src/_standalone)}" +# describe the files that are allowed to not have an associated bats file +BATS_FILE_NOT_NEEDED_REGEXP="${BATS_FILE_NOT_NEEDED_REGEXP:-(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/batsHeaders.sh$|^src/_includes)}" +# describe the files that are allowed to not have a function matching the filename +FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="${FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP:-^bin/|^\.framework-config$|/testsData/|^manualTests/|\.bats$}" +# Source directories +if [[ ! -v FRAMEWORK_SRC_DIRS ]]; then + FRAMEWORK_SRC_DIRS=( + "${FRAMEWORK_ROOT_DIR}/src" + ) +fi + +# export here all the variables that will be used in your templates +export REPOSITORY_URL="${REPOSITORY_URL:-https://github.com/fchastanet/bash-tools-framework}" + +BASH_FRAMEWORK_THEME="${BASH_FRAMEWORK_THEME:-default}" +BASH_FRAMEWORK_LOG_LEVEL="${BASH_FRAMEWORK_LOG_LEVEL:-0}" +BASH_FRAMEWORK_DISPLAY_LEVEL="${BASH_FRAMEWORK_DISPLAY_LEVEL:-3}" +BASH_FRAMEWORK_LOG_FILE="${BASH_FRAMEWORK_LOG_FILE:-${FRAMEWORK_ROOT_DIR}/logs/${0##*/}.log}" +BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION="${BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION:-5}" + +EOF +)" + +overrideEnvFile="$(Framework::createTempFile "overrideEnvFile")" + +commandOptionParseFinished() { + # load default template framework config + defaultEnvFile="${PERSISTENT_TMPDIR}/.framework-config" + echo "${defaultFrameworkConfig}" >"${defaultEnvFile}" + local -a files=("${defaultEnvFile}") + if [[ -f "${envFile}" ]]; then + files+=("${envFile}") fi - if [[ "${INTERACTIVE:-0}" = "1" ]]; then - return 0 + # shellcheck disable=SC2154 + if [[ -f "${optionBashFrameworkConfig}" ]]; then + files+=("${optionBashFrameworkConfig}") + fi + files+=("${overrideEnvFile}") + Env::requireLoad "${files[@]}" + Log::requireLoad + # shellcheck disable=SC2154 + if [[ "${optionConfig}" = "1" ]]; then + displayConfig fi - tty -s } -# FUNCTIONS -facade_main_extractDatash() { -# REQUIRES -Linux::requireRealpathCommand -Env::requireLoad -Log::requireLoad -UI::requireTheme -Compiler::Facade::requireCommandBinDir -# @require Compiler::Facade::requireCommandBinDir -# shellcheck disable=SC2154 +beforeParseCallback() { + BashTools::Conf::requireLoad + Env::requireLoad + UI::requireTheme + Log::requireLoad +} + + + +optionVersionCallback() { + # shellcheck disable=SC2154 + echo "${SCRIPT_NAME} version ${versionNumber}" + Db::checkRequirements + exit 0 +} + + ############################################################ # INTERNAL USE ONLY @@ -858,41 +1582,593 @@ init() { Database::setQueryOptions dbInstance "${dbInstance[QUERY_OPTIONS]} --connect-timeout=5" } -run() { - # extra parameters passed through dbScriptAllDatabases - declare query="${scriptParameters[0]}" - declare queryName="customQuery" - declare queryFile="${query}" - queryFile="$(Conf::getAbsoluteFile "dbQueries" "${queryFile}" "sql" 2>/dev/null || echo "")" - if [[ -n "${queryFile}" ]]; then - queryName="$(basename "${queryFile%.*}")" - query="$(cat "${queryFile}")" - fi +unknownArg() { + : +} - # create log file - declare logFile="" - if [[ "${LOG_FORMAT}" = "log" ]]; then - declare logFile="${outputDir}/${db}_${queryName}.log" - exec 6>&1 1>"${logFile}" # redirect stdout to logFile - fi +beforeParseCallback() { + Env::requireLoad + UI::requireTheme + Log::requireLoad + Linux::requireExecutedAsUser + Linux::requireRealpathCommand + init +} - Database::skipColumnNames dbInstance 0 - Database::query dbInstance "${query}" "${db}" || true - Database::skipColumnNames dbInstance 1 - if [[ "${LOG_FORMAT}" = "log" ]]; then - # restore stdout - exec 1>&6 6>&- - fi +# ------------------------------------------ +# Command extractDataCommand +# ------------------------------------------ + +# options variables initialization +declare optionHelp="0" +declare optionConfig="0" +declare optionBashFrameworkConfig="" +declare optionInfoVerbose="0" +declare optionDebugVerbose="0" +declare optionTraceVerbose="0" +declare -a optionEnvFiles=() +declare optionLogLevel="" +declare optionLogFile="" +declare optionDisplayLevel="" +declare optionNoColor="0" +declare optionTheme="default" +declare optionVersion="0" +declare optionQuiet="0" +# arguments variables initialization +# @description parse command options and arguments for extractDataCommand +extractDataCommandParse() { + Log::displayDebug "Command ${SCRIPT_NAME} - parse arguments: ${BASH_FRAMEWORK_ARGV[*]}" + Log::displayDebug "Command ${SCRIPT_NAME} - parse filtered arguments: ${BASH_FRAMEWORK_ARGV_FILTERED[*]}" + optionHelp="0" + local -i options_parse_optionParsedCountOptionHelp + ((options_parse_optionParsedCountOptionHelp = 0)) || true + optionConfig="0" + local -i options_parse_optionParsedCountOptionConfig + ((options_parse_optionParsedCountOptionConfig = 0)) || true + optionBashFrameworkConfig="" + local -i options_parse_optionParsedCountOptionBashFrameworkConfig + ((options_parse_optionParsedCountOptionBashFrameworkConfig = 0)) || true + optionInfoVerbose="0" + local -i options_parse_optionParsedCountOptionInfoVerbose + ((options_parse_optionParsedCountOptionInfoVerbose = 0)) || true + optionDebugVerbose="0" + local -i options_parse_optionParsedCountOptionDebugVerbose + ((options_parse_optionParsedCountOptionDebugVerbose = 0)) || true + optionTraceVerbose="0" + local -i options_parse_optionParsedCountOptionTraceVerbose + ((options_parse_optionParsedCountOptionTraceVerbose = 0)) || true + + optionLogLevel="" + local -i options_parse_optionParsedCountOptionLogLevel + ((options_parse_optionParsedCountOptionLogLevel = 0)) || true + optionLogFile="" + local -i options_parse_optionParsedCountOptionLogFile + ((options_parse_optionParsedCountOptionLogFile = 0)) || true + optionDisplayLevel="" + local -i options_parse_optionParsedCountOptionDisplayLevel + ((options_parse_optionParsedCountOptionDisplayLevel = 0)) || true + optionNoColor="0" + local -i options_parse_optionParsedCountOptionNoColor + ((options_parse_optionParsedCountOptionNoColor = 0)) || true + optionTheme="default" + local -i options_parse_optionParsedCountOptionTheme + ((options_parse_optionParsedCountOptionTheme = 0)) || true + optionVersion="0" + local -i options_parse_optionParsedCountOptionVersion + ((options_parse_optionParsedCountOptionVersion = 0)) || true + optionQuiet="0" + local -i options_parse_optionParsedCountOptionQuiet + ((options_parse_optionParsedCountOptionQuiet = 0)) || true - if [[ "${LOG_FORMAT}" = "log" ]]; then - Log::displayInfo "result available in '${logFile}'" - fi + + # shellcheck disable=SC2034 + local -i options_parse_parsedArgIndex=0 + while (($# > 0)); do + local options_parse_arg="$1" + local argOptDefaultBehavior=0 + case "${options_parse_arg}" in + # Option 1/14 + # optionHelp alts --help|-h + # type: Boolean min 0 max 1 + --help | -h) + # shellcheck disable=SC2034 + optionHelp="1" + + if ((options_parse_optionParsedCountOptionHelp >= 1 )); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" + return 1 + fi + ((++options_parse_optionParsedCountOptionHelp)) + optionHelpCallback "${options_parse_arg}" "${optionHelp}" + + ;; + + # Option 2/14 + # optionConfig alts --config + # type: Boolean min 0 max 1 + --config) + # shellcheck disable=SC2034 + optionConfig="1" + + if ((options_parse_optionParsedCountOptionConfig >= 1 )); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" + return 1 + fi + ((++options_parse_optionParsedCountOptionConfig)) + ;; + + # Option 3/14 + # optionBashFrameworkConfig alts --bash-framework-config + # type: String min 0 max 1 + --bash-framework-config) + shift + if (($# == 0)); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - a value needs to be specified" + return 1 + fi + + if ((options_parse_optionParsedCountOptionBashFrameworkConfig >= 1 )); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" + return 1 + fi + ((++options_parse_optionParsedCountOptionBashFrameworkConfig)) + # shellcheck disable=SC2034 + optionBashFrameworkConfig="$1" + optionBashFrameworkConfigCallback "${options_parse_arg}" "${optionBashFrameworkConfig}" + + ;; + + # Option 4/14 + # optionInfoVerbose alts --verbose|-v + # type: Boolean min 0 max 1 + --verbose | -v) + # shellcheck disable=SC2034 + optionInfoVerbose="1" + + if ((options_parse_optionParsedCountOptionInfoVerbose >= 1 )); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" + return 1 + fi + ((++options_parse_optionParsedCountOptionInfoVerbose)) + optionInfoVerboseCallback "${options_parse_arg}" "${optionInfoVerbose}" + + updateArgListInfoVerboseCallback "${options_parse_arg}" "${optionInfoVerbose}" + + ;; + + # Option 5/14 + # optionDebugVerbose alts -vv + # type: Boolean min 0 max 1 + -vv) + # shellcheck disable=SC2034 + optionDebugVerbose="1" + + if ((options_parse_optionParsedCountOptionDebugVerbose >= 1 )); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" + return 1 + fi + ((++options_parse_optionParsedCountOptionDebugVerbose)) + optionDebugVerboseCallback "${options_parse_arg}" "${optionDebugVerbose}" + + updateArgListDebugVerboseCallback "${options_parse_arg}" "${optionDebugVerbose}" + + ;; + + # Option 6/14 + # optionTraceVerbose alts -vvv + # type: Boolean min 0 max 1 + -vvv) + # shellcheck disable=SC2034 + optionTraceVerbose="1" + + if ((options_parse_optionParsedCountOptionTraceVerbose >= 1 )); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" + return 1 + fi + ((++options_parse_optionParsedCountOptionTraceVerbose)) + optionTraceVerboseCallback "${options_parse_arg}" "${optionTraceVerbose}" + + updateArgListTraceVerboseCallback "${options_parse_arg}" "${optionTraceVerbose}" + + ;; + + # Option 7/14 + # optionEnvFiles alts --env-file + # type: StringArray min 0 max -1 + --env-file) + shift + if (($# == 0)); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - a value needs to be specified" + return 1 + fi + + ((++options_parse_optionParsedCountOptionEnvFiles)) + optionEnvFiles+=("$1") + optionEnvFileCallback "${options_parse_arg}" "${optionEnvFiles[@]}" + + updateArgListEnvFileCallback "${options_parse_arg}" "${optionEnvFiles[@]}" + + ;; + + # Option 8/14 + # optionLogLevel alts --log-level + # type: String min 0 max 1 + # authorizedValues: OFF|ERR|ERROR|WARN|WARNING|INFO|DEBUG|TRACE + --log-level) + shift + if (($# == 0)); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - a value needs to be specified" + return 1 + fi + if [[ ! "$1" =~ OFF|ERR|ERROR|WARN|WARNING|INFO|DEBUG|TRACE ]]; then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - value '$1' is not part of authorized values([OFF ERR ERROR WARN WARNING INFO DEBUG TRACE])" + return 1 + fi + + if ((options_parse_optionParsedCountOptionLogLevel >= 1 )); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" + return 1 + fi + ((++options_parse_optionParsedCountOptionLogLevel)) + # shellcheck disable=SC2034 + optionLogLevel="$1" + optionLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" + + updateArgListLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" + + ;; + + # Option 9/14 + # optionLogFile alts --log-file + # type: String min 0 max 1 + --log-file) + shift + if (($# == 0)); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - a value needs to be specified" + return 1 + fi + + if ((options_parse_optionParsedCountOptionLogFile >= 1 )); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" + return 1 + fi + ((++options_parse_optionParsedCountOptionLogFile)) + # shellcheck disable=SC2034 + optionLogFile="$1" + optionLogFileCallback "${options_parse_arg}" "${optionLogFile}" + + updateArgListLogFileCallback "${options_parse_arg}" "${optionLogFile}" + + ;; + + # Option 10/14 + # optionDisplayLevel alts --display-level + # type: String min 0 max 1 + # authorizedValues: OFF|ERR|ERROR|WARN|WARNING|INFO|DEBUG|TRACE + --display-level) + shift + if (($# == 0)); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - a value needs to be specified" + return 1 + fi + if [[ ! "$1" =~ OFF|ERR|ERROR|WARN|WARNING|INFO|DEBUG|TRACE ]]; then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - value '$1' is not part of authorized values([OFF ERR ERROR WARN WARNING INFO DEBUG TRACE])" + return 1 + fi + + if ((options_parse_optionParsedCountOptionDisplayLevel >= 1 )); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" + return 1 + fi + ((++options_parse_optionParsedCountOptionDisplayLevel)) + # shellcheck disable=SC2034 + optionDisplayLevel="$1" + optionDisplayLevelCallback "${options_parse_arg}" "${optionDisplayLevel}" + + updateArgListDisplayLevelCallback "${options_parse_arg}" "${optionDisplayLevel}" + + ;; + + # Option 11/14 + # optionNoColor alts --no-color + # type: Boolean min 0 max 1 + --no-color) + # shellcheck disable=SC2034 + optionNoColor="1" + + if ((options_parse_optionParsedCountOptionNoColor >= 1 )); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" + return 1 + fi + ((++options_parse_optionParsedCountOptionNoColor)) + optionNoColorCallback "${options_parse_arg}" "${optionNoColor}" + + updateArgListNoColorCallback "${options_parse_arg}" "${optionNoColor}" + + ;; + + # Option 12/14 + # optionTheme alts --theme + # type: String min 0 max 1 + # authorizedValues: default|default-force|noColor + --theme) + shift + if (($# == 0)); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - a value needs to be specified" + return 1 + fi + if [[ ! "$1" =~ default|default-force|noColor ]]; then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - value '$1' is not part of authorized values([default default-force noColor])" + return 1 + fi + + if ((options_parse_optionParsedCountOptionTheme >= 1 )); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" + return 1 + fi + ((++options_parse_optionParsedCountOptionTheme)) + # shellcheck disable=SC2034 + optionTheme="$1" + optionThemeCallback "${options_parse_arg}" "${optionTheme}" + + updateArgListThemeCallback "${options_parse_arg}" "${optionTheme}" + + ;; + + # Option 13/14 + # optionVersion alts --version + # type: Boolean min 0 max 1 + --version) + # shellcheck disable=SC2034 + optionVersion="1" + + if ((options_parse_optionParsedCountOptionVersion >= 1 )); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" + return 1 + fi + ((++options_parse_optionParsedCountOptionVersion)) + optionVersionCallback "${options_parse_arg}" "${optionVersion}" + + ;; + + # Option 14/14 + # optionQuiet alts --quiet|-q + # type: Boolean min 0 max 1 + --quiet | -q) + # shellcheck disable=SC2034 + optionQuiet="1" + + if ((options_parse_optionParsedCountOptionQuiet >= 1 )); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" + return 1 + fi + ((++options_parse_optionParsedCountOptionQuiet)) + optionQuietCallback "${options_parse_arg}" "${optionQuiet}" + + updateArgListQuietCallback "${options_parse_arg}" "${optionQuiet}" + + ;; + + -*) + if [[ "${argOptDefaultBehavior}" = "0" ]]; then + Log::displayError "Command ${SCRIPT_NAME} - Invalid option ${options_parse_arg}" + return 1 + fi + ;; + *) + # no arg configured, call unknownArgumentCallback + + # shellcheck disable=SC2317 + unknownArg "${options_parse_arg}" + + + if ((incrementArg == 1)); then + ((++options_parse_parsedArgIndex)) + fi + ;; + esac + shift || true + done || return $? + + commandOptionParseFinished + +} + +# @description display command options and arguments help for extractDataCommand +extractDataCommandHelp() { + echo -e "${__HELP_TITLE_COLOR}SYNOPSIS:${__RESET_COLOR}" + Array::wrap2 ' ' 76 4 " " "" + + echo + echo + + # ------------------------------------------ + # usage section + # ------------------------------------------ + Array::wrap2 " " 80 2 "${__HELP_TITLE_COLOR}USAGE:${__RESET_COLOR}" "extractData [OPTIONS] " + echo + # ------------------------------------------ + # usage/options section + # ------------------------------------------ + optionsAltList=("[--help|-h]" "[--config]" "[--bash-framework-config ]" "[--verbose|-v]" "[-vv]" "[-vvv]" "[--env-file ]" "[--log-level ]" "[--log-file ]" "[--display-level ]" "[--no-color]" "[--theme ]" "[--version]" "[--quiet|-q]" + ) + Array::wrap2 " " 80 2 "${__HELP_TITLE_COLOR}USAGE:${__RESET_COLOR}" \ + "extractData" "${optionsAltList[@]}" + echo + + # ------------------------------------------ + # options section + # ------------------------------------------ + echo + echo -e "${__HELP_TITLE_COLOR}GLOBAL OPTIONS:${__RESET_COLOR}" + echo -e " ${__HELP_OPTION_COLOR}--help${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-h${__HELP_NORMAL} {single}" + Array::wrap2 ' ' 76 4 " " "Displays this command help" + echo + + + + echo -e " ${__HELP_OPTION_COLOR}--config${__HELP_NORMAL} {single}" + Array::wrap2 ' ' 76 4 " " "Displays configuration" + echo + + + + echo -e " ${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} {single}" + Array::wrap2 ' ' 76 4 " " "Use alternate bash framework configuration." + echo + + + + echo -e " ${__HELP_OPTION_COLOR}--verbose${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-v${__HELP_NORMAL} {single}" + Array::wrap2 ' ' 76 4 " " "Info level verbose mode (alias of --display-level INFO)" + echo + + + + echo -e " ${__HELP_OPTION_COLOR}-vv${__HELP_NORMAL} {single}" + Array::wrap2 ' ' 76 4 " " "Debug level verbose mode (alias of --display-level DEBUG)" + echo + + + + echo -e " ${__HELP_OPTION_COLOR}-vvv${__HELP_NORMAL} {single}" + Array::wrap2 ' ' 76 4 " " "Trace level verbose mode (alias of --display-level TRACE)" + echo + + + + echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" + Array::wrap2 ' ' 76 4 " " "Load the specified env file (deprecated, please use --bash-framework-config option instead)" + echo + + + + echo -e " ${__HELP_OPTION_COLOR}--log-level ${__HELP_NORMAL} {single}" + Array::wrap2 ' ' 76 4 " " "Set log level" + echo + Array::wrap2 ' ' 76 6 " Possible values: " "OFF, " "ERR, " "ERROR, " "WARN, " "WARNING, " "INFO, " "DEBUG, " "TRACE" + echo + + + echo -e " ${__HELP_OPTION_COLOR}--log-file ${__HELP_NORMAL} {single}" + Array::wrap2 ' ' 76 4 " " "Set log file" + echo + + + + echo -e " ${__HELP_OPTION_COLOR}--display-level ${__HELP_NORMAL} {single}" + Array::wrap2 ' ' 76 4 " " "Set display level" + echo + Array::wrap2 ' ' 76 6 " Possible values: " "OFF, " "ERR, " "ERROR, " "WARN, " "WARNING, " "INFO, " "DEBUG, " "TRACE" + echo + + + echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" + Array::wrap2 ' ' 76 4 " " "Produce monochrome output. alias of --theme noColor." + echo + + + + echo -e " ${__HELP_OPTION_COLOR}--theme ${__HELP_NORMAL} {single}" + Array::wrap2 ' ' 76 4 " " "Choose color theme - default-force means colors will be produced even if command is piped." + echo + Array::wrap2 ' ' 76 6 " Possible values: " "default, " "default-force, " "noColor" + echo + + Array::wrap2 ' ' 76 6 " Default value: " "default" + echo + + echo -e " ${__HELP_OPTION_COLOR}--version${__HELP_NORMAL} {single}" + Array::wrap2 ' ' 76 4 " " "Print version information and quit." + echo + + + + echo -e " ${__HELP_OPTION_COLOR}--quiet${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-q${__HELP_NORMAL} {single}" + Array::wrap2 ' ' 76 4 " " "Quiet mode, doesn't display any output." + echo + + + # ------------------------------------------ + # version section + # ------------------------------------------ + echo + echo -n -e "${__HELP_TITLE_COLOR}VERSION: ${__RESET_COLOR}" + echo "2.0" + # ------------------------------------------ + # author section + # ------------------------------------------ + echo + echo -n -e "${__HELP_TITLE_COLOR}AUTHOR: ${__RESET_COLOR}" + echo "[François Chastanet](https://github.com/fchastanet)" + # ------------------------------------------ + # sourceFile section + # ------------------------------------------ + echo + echo -n -e "${__HELP_TITLE_COLOR}SOURCE FILE: ${__RESET_COLOR}" + echo "https://github.com/fchastanet/bash-tools-framework/tree/master/src/_binaries/Database/extractData/binary-extractData.yaml" + # ------------------------------------------ + # license section + # ------------------------------------------ + echo + echo -n -e "${__HELP_TITLE_COLOR}LICENSE: ${__RESET_COLOR}" + echo "MIT License" + # ------------------------------------------ + # copyright section + # ------------------------------------------ + Array::wrap2 ' ' 76 0 "$(copyrightCallback)" } -init -run + +beforeParseCallback + +extractDataCommandParse "$@" +MAIN_FUNCTION_NAME="main" +main() { + +# extra parameters passed through dbScriptAllDatabases +# shellcheck disable=SC2154 +declare query="${scriptParameters[0]}" +declare queryName="customQuery" +declare queryFile="${query}" +queryFile="$(Conf::getAbsoluteFile "dbQueries" "${queryFile}" "sql" 2>/dev/null || echo "")" +if [[ -n "${queryFile}" ]]; then + queryName="$(basename "${queryFile%.*}")" + query="$(cat "${queryFile}")" +fi + +# create log file +declare logFile="" +if [[ "${LOG_FORMAT}" = "log" ]]; then + # shellcheck disable=SC2154 + declare logFile="${outputDir}/${db}_${queryName}.log" + exec 6>&1 1>"${logFile}" # redirect stdout to logFile +fi + +Database::skipColumnNames dbInstance 0 +Database::query dbInstance "${query}" "${db}" || true +Database::skipColumnNames dbInstance 1 + +if [[ "${LOG_FORMAT}" = "log" ]]; then + # restore stdout + exec 1>&6 6>&- +fi + +if [[ "${LOG_FORMAT}" = "log" ]]; then + Log::displayInfo "result available in '${logFile}'" +fi } -facade_main_extractDatash "$@" +# if file is sourced avoid calling main function +# shellcheck disable=SC2178 +BASH_SOURCE=".$0" # cannot be changed in bash +# shellcheck disable=SC2128 +if test ".$0" == ".${BASH_SOURCE}"; then + if [[ "${BASH_FRAMEWORK_QUIET_MODE:-0}" = "1" ]]; then + main "$@" &>/dev/null + else + main "$@" + fi +fi diff --git a/src/_binaries/Database/dbScriptAllDatabases/binary-extractData.yaml b/src/_binaries/Database/dbScriptAllDatabases/binary-extractData.yaml new file mode 100644 index 00000000..3318b9d6 --- /dev/null +++ b/src/_binaries/Database/dbScriptAllDatabases/binary-extractData.yaml @@ -0,0 +1,27 @@ +extends: + - "${BASH_TOOLS_ROOT_DIR}/src/_binaries/commandDefinitions/optionsVersion.yaml" + - "${FRAMEWORK_ROOT_DIR}/src/_binaries/commandDefinitions/defaultCommand.yaml" + - "${FRAMEWORK_ROOT_DIR}/src/_binaries/commandDefinitions/frameworkConfig.yaml" + +vars: + SRC_FILE_PATH: src/_binaries/Database/extractData/binary-extractData.yaml + +compilerConfig: + targetFile: "${BASH_TOOLS_ROOT_DIR}/conf/dbScripts/extractData" + relativeRootDirBasedOnTargetDir: ../.. + srcDirs: + - ${BASH_TOOLS_ROOT_DIR}/src +binData: + commands: + default: + functionName: extractDataCommand + version: "2.0" + commandName: extractData + beforeParseCallbacks: + - beforeParseCallback + unknownArgumentCallbacks: + - unknownArg + definitionFiles: + 11: ${BASH_TOOLS_ROOT_DIR}/src/_binaries/commandDefinitions/optionsDefault.sh + 99: ${BASH_TOOLS_ROOT_DIR}/src/_binaries/Database/dbScriptAllDatabases/dbScriptOneDatabase.sh + mainFile: ${BASH_TOOLS_ROOT_DIR}/src/_binaries/Database/dbScriptAllDatabases/extractData.sh diff --git a/src/_includes/dbScriptOneDatabase.sh b/src/_binaries/Database/dbScriptAllDatabases/dbScriptOneDatabase.sh similarity index 86% rename from src/_includes/dbScriptOneDatabase.sh rename to src/_binaries/Database/dbScriptAllDatabases/dbScriptOneDatabase.sh index c378d6a5..9e0b7b86 100755 --- a/src/_includes/dbScriptOneDatabase.sh +++ b/src/_binaries/Database/dbScriptAllDatabases/dbScriptOneDatabase.sh @@ -38,3 +38,16 @@ init() { Database::newInstance dbInstance "${DSN}" Database::setQueryOptions dbInstance "${dbInstance[QUERY_OPTIONS]} --connect-timeout=5" } + +unknownArg() { + : +} + +beforeParseCallback() { + Env::requireLoad + UI::requireTheme + Log::requireLoad + Linux::requireExecutedAsUser + Linux::requireRealpathCommand + init +} diff --git a/src/_binaries/Database/dbScriptAllDatabases/extractData.sh b/src/_binaries/Database/dbScriptAllDatabases/extractData.sh new file mode 100755 index 00000000..5be2ceea --- /dev/null +++ b/src/_binaries/Database/dbScriptAllDatabases/extractData.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash + +# extra parameters passed through dbScriptAllDatabases +# shellcheck disable=SC2154 +declare query="${scriptParameters[0]}" +declare queryName="customQuery" +declare queryFile="${query}" +queryFile="$(Conf::getAbsoluteFile "dbQueries" "${queryFile}" "sql" 2>/dev/null || echo "")" +if [[ -n "${queryFile}" ]]; then + queryName="$(basename "${queryFile%.*}")" + query="$(cat "${queryFile}")" +fi + +# create log file +declare logFile="" +if [[ "${LOG_FORMAT}" = "log" ]]; then + # shellcheck disable=SC2154 + declare logFile="${outputDir}/${db}_${queryName}.log" + exec 6>&1 1>"${logFile}" # redirect stdout to logFile +fi + +Database::skipColumnNames dbInstance 0 +Database::query dbInstance "${query}" "${db}" || true +Database::skipColumnNames dbInstance 1 + +if [[ "${LOG_FORMAT}" = "log" ]]; then + # restore stdout + exec 1>&6 6>&- +fi + +if [[ "${LOG_FORMAT}" = "log" ]]; then + Log::displayInfo "result available in '${logFile}'" +fi diff --git a/src/_binaries/Database/dbScriptAllDatabases/testsData/dbScriptAllDatabases.help.txt b/src/_binaries/Database/dbScriptAllDatabases/testsData/dbScriptAllDatabases.help.txt index b6f9311a..d828df61 100644 --- a/src/_binaries/Database/dbScriptAllDatabases/testsData/dbScriptAllDatabases.help.txt +++ b/src/_binaries/Database/dbScriptAllDatabases/testsData/dbScriptAllDatabases.help.txt @@ -1,6 +1,7 @@ SYNOPSIS: Allows to execute a script on each database of specified mysql server. + USAGE: dbScriptAllDatabases [OPTIONS] [ARGUMENTS] USAGE: dbScriptAllDatabases [--jobs|-j ] [--bar|-b] [--from-dsn|-f ] [--help|-h] [--config] diff --git a/src/_binaries/DbScriptAllDatabases/extractData.sh b/src/_binaries/DbScriptAllDatabases/extractData.sh deleted file mode 100755 index a32f2b7a..00000000 --- a/src/_binaries/DbScriptAllDatabases/extractData.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash -# BIN_FILE=${BASH_TOOLS_ROOT_DIR}/conf/dbScripts/extractData -# FACADE -# shellcheck disable=SC2154 - -.INCLUDE "$(dynamicSrcFile _includes/dbScriptOneDatabase.sh)" - -run() { - # extra parameters passed through dbScriptAllDatabases - declare query="${scriptParameters[0]}" - declare queryName="customQuery" - declare queryFile="${query}" - queryFile="$(Conf::getAbsoluteFile "dbQueries" "${queryFile}" "sql" 2>/dev/null || echo "")" - if [[ -n "${queryFile}" ]]; then - queryName="$(basename "${queryFile%.*}")" - query="$(cat "${queryFile}")" - fi - - # create log file - declare logFile="" - if [[ "${LOG_FORMAT}" = "log" ]]; then - declare logFile="${outputDir}/${db}_${queryName}.log" - exec 6>&1 1>"${logFile}" # redirect stdout to logFile - fi - - Database::skipColumnNames dbInstance 0 - Database::query dbInstance "${query}" "${db}" || true - Database::skipColumnNames dbInstance 1 - - if [[ "${LOG_FORMAT}" = "log" ]]; then - # restore stdout - exec 1>&6 6>&- - fi - - if [[ "${LOG_FORMAT}" = "log" ]]; then - Log::displayInfo "result available in '${logFile}'" - fi -} - -init -run