From 6e2670ea447d166da2abe9072d2c2b555567681c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chastanet?= Date: Sun, 19 Jan 2025 18:39:28 +0100 Subject: [PATCH] Enhanced software release retrieval Updated: - Web::upgradeRelease - Web::getReleases - Github::installRelease Changes: - support for gh - allows to override version place holder using env variable VERSION_PLACEHOLDER --- bin/doc | 46 +++-- bin/dockerLint | 68 +++++-- bin/installFacadeExample | 66 +++--- bin/installRequirements | 46 +++-- bin/megalinter | 36 +++- doc/images/activityDiagram.png | Bin 0 -> 24260 bytes src/Env/activityDiagram.png | Bin 24258 -> 24260 bytes src/Filters/firstField.bats | 2 +- src/Filters/testsData/binary | 359 ++++++++++++--------------------- src/Github/getLatestRelease.sh | 36 +++- src/Github/installRelease.sh | 6 +- src/Github/upgradeRelease.sh | 1 + src/Web/getReleases.sh | 27 ++- src/Web/upgradeRelease.sh | 18 +- 14 files changed, 377 insertions(+), 334 deletions(-) create mode 100644 doc/images/activityDiagram.png diff --git a/bin/doc b/bin/doc index ed4a3540..9ff04463 100755 --- a/bin/doc +++ b/bin/doc @@ -677,6 +677,7 @@ Github::upgradeRelease() { } FILTER_LAST_VERSION_CALLBACK=${FILTER_LAST_VERSION_CALLBACK:-extractVersion} \ SOFT_VERSION_CALLBACK="${softVersionCallback}" \ + INSTALL_CALLBACK="${installCallback}" \ Web::upgradeRelease \ "${targetFile}" \ "${releasesUrl}" \ @@ -1544,12 +1545,27 @@ Version::parse() { Web::getReleases() { local releaseListUrl="$1" # Get latest release from GitHub api - Retry::parameterized "${RETRY_MAX_RETRY:-5}" "${RETRY_DELAY_BETWEEN_RETRIES:-15}" "Retrieving release versions list ..." curl \ - -L \ - --connect-timeout "${CURL_CONNECT_TIMEOUT:-5}" \ - --fail \ - --silent \ - "${releaseListUrl}" + if command -v gh &>/dev/null && [[ -n "${GH_TOKEN}" ]]; then + Log::displayDebug "Using gh to retrieve release versions list" + Retry::parameterized "${RETRY_MAX_RETRY:-5}" \ + "${RETRY_DELAY_BETWEEN_RETRIES:-15}" \ + "Retrieving release versions list ..." \ + gh api "${releaseListUrl#https://github.com}" + else + if command -v gh &>/dev/null && [[ "${GH_WARNING_DISPLAYED:-0}" = "0" ]]; then + Log::displayWarning "GH_TOKEN is not set, cannot use gh, using curl to retrieve release versions list" + GH_WARNING_DISPLAYED=1 + fi + Retry::parameterized "${RETRY_MAX_RETRY:-5}" \ + "${RETRY_DELAY_BETWEEN_RETRIES:-15}" \ + "Retrieving release versions list ..." \ + curl \ + -L \ + --connect-timeout "${CURL_CONNECT_TIMEOUT:-5}" \ + --fail \ + --silent \ + "${releaseListUrl}" + fi } @@ -1567,6 +1583,7 @@ Web::getReleases() { # @env PARSE_VERSION_CALLBACK a callback to parse the version of the existing command # @env INSTALL_CALLBACK a callback to install the software downloaded # @env CURL_CONNECT_TIMEOUT number of seconds before giving up host connection +# @env VERSION_PLACEHOLDER a placeholder to replace in downloadReleaseUrl (default: @latestVersion@) Web::upgradeRelease() { local targetFile="$1" local releasesUrl="$2" @@ -1577,25 +1594,28 @@ Web::upgradeRelease() { local filterLastVersionCallback="${FILTER_LAST_VERSION_CALLBACK:-Version::parse}" local softVersionCallback="${SOFT_VERSION_CALLBACK:-Version::getCommandVersionFromPlainText}" local installCallback="${INSTALL_CALLBACK:-}" - local latestVersion - latestVersion="$(Web::getReleases "${releasesUrl}" | ${filterLastVersionCallback})" || { - Log::displayError "latest version not found on ${releasesUrl}" - return 1 - } - Log::displayInfo "Latest version found is ${latestVersion}" local currentVersion="not existing" if [[ -f "${targetFile}" ]]; then currentVersion="$(${softVersionCallback} "${targetFile}" "${softVersionArg}" 2>&1 || true)" fi if [[ -z "${exactVersion}" ]]; then + local latestVersion + latestVersion="$(Web::getReleases "${releasesUrl}" | ${filterLastVersionCallback})" || { + Log::displayError "latest version not found on ${releasesUrl}" + return 1 + } + Log::displayInfo "Latest version found is ${latestVersion}" + exactVersion="${latestVersion}" fi - local url="${downloadReleaseUrl//@latestVersion@/${exactVersion}}" + local url="${downloadReleaseUrl//${VERSION_PLACEHOLDER:-@latestVersion@}/${exactVersion}}" if [[ -n "${exactVersion}" ]] && ! Github::isReleaseVersionExist "${url}"; then Log::displayError "${targetFile} version ${exactVersion} doesn't exist on github" return 2 fi + Log::displayDebug "currentVersion: '${currentVersion}'" + Log::displayDebug "exactVersion: '${exactVersion}'" if [[ "${currentVersion}" = "${exactVersion}" ]]; then Log::displayInfo "${targetFile} version ${exactVersion} already installed" else diff --git a/bin/dockerLint b/bin/dockerLint index 80ea2f8a..6be8cb2c 100755 --- a/bin/dockerLint +++ b/bin/dockerLint @@ -488,6 +488,7 @@ Github::upgradeRelease() { } FILTER_LAST_VERSION_CALLBACK=${FILTER_LAST_VERSION_CALLBACK:-extractVersion} \ SOFT_VERSION_CALLBACK="${softVersionCallback}" \ + INSTALL_CALLBACK="${installCallback}" \ Web::upgradeRelease \ "${targetFile}" \ "${releasesUrl}" \ @@ -612,6 +613,19 @@ Log::displaySuccess() { } +# @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 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 @@ -704,6 +718,15 @@ Log::logSuccess() { } +# @description log message to file +# @arg $1 message:String the message to display +Log::logWarning() { + if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_WARNING)); then + Log::logMessage "${2:-WARNING}" "$1" + fi +} + + # @description activate or not Log::display* and Log::log* functions # based on BASH_FRAMEWORK_DISPLAY_LEVEL and BASH_FRAMEWORK_LOG_LEVEL # environment variables loaded by Env::requireLoad @@ -1074,12 +1097,27 @@ Version::parse() { Web::getReleases() { local releaseListUrl="$1" # Get latest release from GitHub api - Retry::parameterized "${RETRY_MAX_RETRY:-5}" "${RETRY_DELAY_BETWEEN_RETRIES:-15}" "Retrieving release versions list ..." curl \ - -L \ - --connect-timeout "${CURL_CONNECT_TIMEOUT:-5}" \ - --fail \ - --silent \ - "${releaseListUrl}" + if command -v gh &>/dev/null && [[ -n "${GH_TOKEN}" ]]; then + Log::displayDebug "Using gh to retrieve release versions list" + Retry::parameterized "${RETRY_MAX_RETRY:-5}" \ + "${RETRY_DELAY_BETWEEN_RETRIES:-15}" \ + "Retrieving release versions list ..." \ + gh api "${releaseListUrl#https://github.com}" + else + if command -v gh &>/dev/null && [[ "${GH_WARNING_DISPLAYED:-0}" = "0" ]]; then + Log::displayWarning "GH_TOKEN is not set, cannot use gh, using curl to retrieve release versions list" + GH_WARNING_DISPLAYED=1 + fi + Retry::parameterized "${RETRY_MAX_RETRY:-5}" \ + "${RETRY_DELAY_BETWEEN_RETRIES:-15}" \ + "Retrieving release versions list ..." \ + curl \ + -L \ + --connect-timeout "${CURL_CONNECT_TIMEOUT:-5}" \ + --fail \ + --silent \ + "${releaseListUrl}" + fi } @@ -1097,6 +1135,7 @@ Web::getReleases() { # @env PARSE_VERSION_CALLBACK a callback to parse the version of the existing command # @env INSTALL_CALLBACK a callback to install the software downloaded # @env CURL_CONNECT_TIMEOUT number of seconds before giving up host connection +# @env VERSION_PLACEHOLDER a placeholder to replace in downloadReleaseUrl (default: @latestVersion@) Web::upgradeRelease() { local targetFile="$1" local releasesUrl="$2" @@ -1107,25 +1146,28 @@ Web::upgradeRelease() { local filterLastVersionCallback="${FILTER_LAST_VERSION_CALLBACK:-Version::parse}" local softVersionCallback="${SOFT_VERSION_CALLBACK:-Version::getCommandVersionFromPlainText}" local installCallback="${INSTALL_CALLBACK:-}" - local latestVersion - latestVersion="$(Web::getReleases "${releasesUrl}" | ${filterLastVersionCallback})" || { - Log::displayError "latest version not found on ${releasesUrl}" - return 1 - } - Log::displayInfo "Latest version found is ${latestVersion}" local currentVersion="not existing" if [[ -f "${targetFile}" ]]; then currentVersion="$(${softVersionCallback} "${targetFile}" "${softVersionArg}" 2>&1 || true)" fi if [[ -z "${exactVersion}" ]]; then + local latestVersion + latestVersion="$(Web::getReleases "${releasesUrl}" | ${filterLastVersionCallback})" || { + Log::displayError "latest version not found on ${releasesUrl}" + return 1 + } + Log::displayInfo "Latest version found is ${latestVersion}" + exactVersion="${latestVersion}" fi - local url="${downloadReleaseUrl//@latestVersion@/${exactVersion}}" + local url="${downloadReleaseUrl//${VERSION_PLACEHOLDER:-@latestVersion@}/${exactVersion}}" if [[ -n "${exactVersion}" ]] && ! Github::isReleaseVersionExist "${url}"; then Log::displayError "${targetFile} version ${exactVersion} doesn't exist on github" return 2 fi + Log::displayDebug "currentVersion: '${currentVersion}'" + Log::displayDebug "exactVersion: '${exactVersion}'" if [[ "${currentVersion}" = "${exactVersion}" ]]; then Log::displayInfo "${targetFile} version ${exactVersion} already installed" else diff --git a/bin/installFacadeExample b/bin/installFacadeExample index c5dc2a84..6373e080 100755 --- a/bin/installFacadeExample +++ b/bin/installFacadeExample @@ -516,39 +516,39 @@ Log::logFatal() { # FUNCTIONS facade_main_installFacadeExamplesh() { -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" -# REQUIRES -Env::requireLoad -Log::requireLoad -UI::requireTheme -Compiler::Facade::requireCommandBinDir - -# @require Compiler::Facade::requireCommandBinDir - -install() { - echo "installation in progress" -} - -local action=$1 -shift || true -case ${action} in - install) - install "$@" - ;; - *) - if Assert::functionExists defaultFacadeAction; then - defaultFacadeAction "$1" "$@" - else - Log::displayError "invalid action requested: ${action}" - exit 1 - fi - ;; -esac -exit 0 + 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" + # REQUIRES + Env::requireLoad + Log::requireLoad + UI::requireTheme + Compiler::Facade::requireCommandBinDir + + # @require Compiler::Facade::requireCommandBinDir + + install() { + echo "installation in progress" + } + + local action=$1 + shift || true + case ${action} in + install) + install "$@" + ;; + *) + if Assert::functionExists defaultFacadeAction; then + defaultFacadeAction "$1" "$@" + else + Log::displayError "invalid action requested: ${action}" + exit 1 + fi + ;; + esac + exit 0 } # if file is sourced avoid calling main function diff --git a/bin/installRequirements b/bin/installRequirements index de442b81..16702b3b 100755 --- a/bin/installRequirements +++ b/bin/installRequirements @@ -596,6 +596,7 @@ Github::upgradeRelease() { } FILTER_LAST_VERSION_CALLBACK=${FILTER_LAST_VERSION_CALLBACK:-extractVersion} \ SOFT_VERSION_CALLBACK="${softVersionCallback}" \ + INSTALL_CALLBACK="${installCallback}" \ Web::upgradeRelease \ "${targetFile}" \ "${releasesUrl}" \ @@ -1172,12 +1173,27 @@ Version::parse() { Web::getReleases() { local releaseListUrl="$1" # Get latest release from GitHub api - Retry::parameterized "${RETRY_MAX_RETRY:-5}" "${RETRY_DELAY_BETWEEN_RETRIES:-15}" "Retrieving release versions list ..." curl \ - -L \ - --connect-timeout "${CURL_CONNECT_TIMEOUT:-5}" \ - --fail \ - --silent \ - "${releaseListUrl}" + if command -v gh &>/dev/null && [[ -n "${GH_TOKEN}" ]]; then + Log::displayDebug "Using gh to retrieve release versions list" + Retry::parameterized "${RETRY_MAX_RETRY:-5}" \ + "${RETRY_DELAY_BETWEEN_RETRIES:-15}" \ + "Retrieving release versions list ..." \ + gh api "${releaseListUrl#https://github.com}" + else + if command -v gh &>/dev/null && [[ "${GH_WARNING_DISPLAYED:-0}" = "0" ]]; then + Log::displayWarning "GH_TOKEN is not set, cannot use gh, using curl to retrieve release versions list" + GH_WARNING_DISPLAYED=1 + fi + Retry::parameterized "${RETRY_MAX_RETRY:-5}" \ + "${RETRY_DELAY_BETWEEN_RETRIES:-15}" \ + "Retrieving release versions list ..." \ + curl \ + -L \ + --connect-timeout "${CURL_CONNECT_TIMEOUT:-5}" \ + --fail \ + --silent \ + "${releaseListUrl}" + fi } @@ -1195,6 +1211,7 @@ Web::getReleases() { # @env PARSE_VERSION_CALLBACK a callback to parse the version of the existing command # @env INSTALL_CALLBACK a callback to install the software downloaded # @env CURL_CONNECT_TIMEOUT number of seconds before giving up host connection +# @env VERSION_PLACEHOLDER a placeholder to replace in downloadReleaseUrl (default: @latestVersion@) Web::upgradeRelease() { local targetFile="$1" local releasesUrl="$2" @@ -1205,25 +1222,28 @@ Web::upgradeRelease() { local filterLastVersionCallback="${FILTER_LAST_VERSION_CALLBACK:-Version::parse}" local softVersionCallback="${SOFT_VERSION_CALLBACK:-Version::getCommandVersionFromPlainText}" local installCallback="${INSTALL_CALLBACK:-}" - local latestVersion - latestVersion="$(Web::getReleases "${releasesUrl}" | ${filterLastVersionCallback})" || { - Log::displayError "latest version not found on ${releasesUrl}" - return 1 - } - Log::displayInfo "Latest version found is ${latestVersion}" local currentVersion="not existing" if [[ -f "${targetFile}" ]]; then currentVersion="$(${softVersionCallback} "${targetFile}" "${softVersionArg}" 2>&1 || true)" fi if [[ -z "${exactVersion}" ]]; then + local latestVersion + latestVersion="$(Web::getReleases "${releasesUrl}" | ${filterLastVersionCallback})" || { + Log::displayError "latest version not found on ${releasesUrl}" + return 1 + } + Log::displayInfo "Latest version found is ${latestVersion}" + exactVersion="${latestVersion}" fi - local url="${downloadReleaseUrl//@latestVersion@/${exactVersion}}" + local url="${downloadReleaseUrl//${VERSION_PLACEHOLDER:-@latestVersion@}/${exactVersion}}" if [[ -n "${exactVersion}" ]] && ! Github::isReleaseVersionExist "${url}"; then Log::displayError "${targetFile} version ${exactVersion} doesn't exist on github" return 2 fi + Log::displayDebug "currentVersion: '${currentVersion}'" + Log::displayDebug "exactVersion: '${exactVersion}'" if [[ "${currentVersion}" = "${exactVersion}" ]]; then Log::displayInfo "${targetFile} version ${exactVersion} already installed" else diff --git a/bin/megalinter b/bin/megalinter index 8b984cd4..5c18df67 100755 --- a/bin/megalinter +++ b/bin/megalinter @@ -382,17 +382,31 @@ Github::getLatestRelease() { resultRef="" local resultFile resultFile="$(mktemp -p "${TMPDIR:-/tmp}" -t githubLatestRelease.XXXX)" - # Get latest release from GitHub api - if Retry::default curl \ - -L \ - --connect-timeout "${CURL_CONNECT_TIMEOUT:-5}" \ - -o "${resultFile}" \ - --fail \ - --silent \ - "https://api.github.com/repos/${repo}/releases/latest"; then - # shellcheck disable=SC2034 - resultRef="$(Version::githubApiExtractVersion <"${resultFile}")" - return 0 + local query="repos/${repo}/releases/latest" + + if command -v gh &>/dev/null && [[ -n "${GH_TOKEN}" ]]; then + Log::displayDebug "Using gh to retrieve release versions list" + Retry::parameterized "${RETRY_MAX_RETRY:-5}" \ + "${RETRY_DELAY_BETWEEN_RETRIES:-15}" \ + "Retrieving release versions list ..." \ + gh api "${query}" >"${resultFile}" + else + if command -v gh &>/dev/null && [[ "${GH_WARNING_DISPLAYED:-0}" = "0" ]]; then + Log::displayWarning "GH_TOKEN is not set, cannot use gh, using curl to retrieve release versions list" + GH_WARNING_DISPLAYED=1 + fi + # Get latest release from GitHub api + if Retry::default curl \ + -L \ + --connect-timeout "${CURL_CONNECT_TIMEOUT:-5}" \ + -o "${resultFile}" \ + --fail \ + --silent \ + "https://api.github.com/${query}"; then + # shellcheck disable=SC2034 + resultRef="$(Version::githubApiExtractVersion <"${resultFile}")" + return 0 + fi fi # display curl result in case of failure cat >&2 "${resultFile}" diff --git a/doc/images/activityDiagram.png b/doc/images/activityDiagram.png new file mode 100644 index 0000000000000000000000000000000000000000..3625aacee57a6c4eecf6fe93397284a11f99d8b4 GIT binary patch literal 24260 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 v%779rY83Q)F~SBS&mKGYzW=XK>^W?j`9 literal 0 HcmV?d00001 diff --git a/src/Env/activityDiagram.png b/src/Env/activityDiagram.png index 5413a20894c4b3189abe2b45c46394c4cc01a366..3625aacee57a6c4eecf6fe93397284a11f99d8b4 100644 GIT binary patch delta 625 zcmV-%0*?K{y#d6%0gzgMR%ujNbZ~58Zgh2RYybfO004NL?Nm)~+b|5h>sJs-fi;K5 zaqE1!+t4O))}_u;#BFvgWQ%dM+L9;B#)6{%eX`Rw-GbeA+jNph>XCeW;6<*f(nXp; z14X&S?*&)rOUfEVbDbcloe7MWD=AVG8WQ8@A}09*7a3JFg+~H^@D4UwXSr)xG1r?S zY)3M+vV@8Xet!uQ8CuTJw!3T{^_gRLd&8cM-Ow4*fgN>*q0@I5rTyXH^vudtWC_$( zE(;amTB;cLP`TbFXib$&gO^HfWl$O0-)KpjzU_2PVKmabwiLOGqVb>ep;6IgQi)6W-7JeeIyxvtgn9p`fyyk0UAetk5`Mi_hhw0nr}|Dlu2&2 zjSw0K0k6i^E{nBKgMw8>k;Aqau4$2ID5>H!G*rOa5K{H=R7L+1Tl1Hq8Gl7xO&lD_ z12WHd(~=pvFnNte2)X!s3jZ&pe_2R3)!Mn6`%t3Ft6A9X)6QwP-8sHBY|p5GP9N;P zd)9T&dT@7RJBG^LBFCR@eAw+TU_94~heb8Z7MYnP1sRa~%oGkp zm^xpk?LZQz6dc0DK#z=UZ5$O_a%I`m48wn~>;R$7^wwLeleD{fyZiRdo)(5_Q)Vf& zP*w~4UJ8wYV!TC+Fe!rGn8134Rx(3rAhmuW6H+_~nKR8Ycq9OS?_gz2UU-xy!mP@u z6RV8oDU&t)_7bHkqVCXfx_lY;x$AU$!=8iP$Q`nQ6CVvDx9@Vs`oqETds^t2N;I@k zrH*l_bb?!`!mLxIQ?0V_rIH&J*2c~^RXUKd4n+v2!nWon>eTCm7c0ZU6r-N#cM{Yz}aUxs%4HFXVfu%~v& zJlRZ3<>b`nwHhJh^zSMBzmWcAAzjyN=XMrAg}SI`;dG9Vj@`~+f2svssS2DfIDOA` zJ+}w9H)E(_-3Mg<^K}55{RLbUMvJJdXL&#_?2>{-DUHa`gL#hfVsiOn0Dr4Ta&b4? z2K3y+kuVUVww1u$j7*;U_oZda%GR^lr-uU)FqxDrvDhfd*b3F?we^MMCrnJ~S9_(P Jc(b_z3taZpI+y?e diff --git a/src/Filters/firstField.bats b/src/Filters/firstField.bats index b2da37af..9c4ba0bf 100755 --- a/src/Filters/firstField.bats +++ b/src/Filters/firstField.bats @@ -31,7 +31,7 @@ function Filters::firstField::bash { #@test } function Filters::firstField::bigFile { #@test - tail -1209 "${BATS_TEST_DIRNAME}/testsData/binary" | { + tail -1170 "${BATS_TEST_DIRNAME}/testsData/binary" | { run Filters::firstField assert_success assert_output 'Github::upgradeRelease' diff --git a/src/Filters/testsData/binary b/src/Filters/testsData/binary index 63db94e7..9392aa23 100755 --- a/src/Filters/testsData/binary +++ b/src/Filters/testsData/binary @@ -6,8 +6,6 @@ ############################################################################### # shellcheck disable=SC2288,SC2034 - - # ensure that no user aliases could interfere with # commands used in this script unalias -a || true @@ -94,7 +92,6 @@ 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 @@ -138,7 +135,6 @@ export __VERBOSE_LEVEL_DEBUG=2 # @description verbose level info export __VERBOSE_LEVEL_TRACE=3 - # @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. @@ -267,7 +263,6 @@ Array::wrap2() { ) | sed -E -e 's/[[:blank:]]+$//' } - # @description check if command specified exists or return 1 # with error and message if not # @@ -290,7 +285,6 @@ Assert::commandExists() { return 0 } - # @description check if tty (interactive mode) is active # @noargs # @exitcode 1 if tty not active @@ -306,7 +300,6 @@ Assert::tty() { tty -s } - # @description get file content if file not expired # @arg $1 file:String the file to get content from # @arg $2 maxDuration:int number of seconds after which the file is considered expired @@ -326,7 +319,6 @@ Cache::getFileContentIfNotExpired() { cat "${file}" } - # @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 @@ -356,7 +348,6 @@ Compiler::Embed::extractFileFromBase64() { fi } - # @description prepend directories to the PATH environment variable # @arg $@ args:String[] list of directories to prepend # @set PATH update PATH with the directories prepended @@ -369,7 +360,6 @@ Env::pathPrepend() { 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 @@ -405,7 +395,6 @@ Env::requireLoad() { done } - # @description get number of seconds since last modification of the file # @arg $1 file:String file path # @exitcode 1 if file does not exist @@ -421,7 +410,6 @@ File::elapsedTimeSinceLastModification() { echo -n "${diff}" } - # @description replace token by input(stdin) in given targetFile # @warning special ansi codes will be removed from stdin # @arg $1 token:String the token to replace by stdin @@ -447,7 +435,6 @@ File::replaceTokenByInput() { ) } - # @description remove ansi codes from input or files given as argument # @arg $@ files:String[] the files to filter # @exitcode * if one of the filter command fails @@ -461,7 +448,6 @@ Filters::removeAnsiCodes() { # cspell:enable } - # @description create a temp file using default TMPDIR variable # initialized in _includes/_commonHeader.sh # @env TMPDIR String (default value /tmp) @@ -470,7 +456,6 @@ Framework::createTempFile() { mktemp -p "${TMPDIR:-/tmp}" -t "${1:-}.XXXXXXXXXXXX" } - # @description clone the repository if not done yet, else pull it if no change in it # @arg $1 dir:String directory in which repository is installed or will be cloned # @arg $2 repo:String repository url @@ -520,7 +505,6 @@ Git::cloneOrPullIfNoChanges() { fi } - # @description pull git directory only if no change has been detected # @arg $1 dir:String the git directory to pull # @exitcode 0 on successful pulling @@ -561,7 +545,6 @@ Git::pullIfNoChanges() { ) } - # @description ensure command git is available # @exitcode 1 if git command not available # @stderr diagnostics information is displayed @@ -571,7 +554,6 @@ Git::requireGitCommand() { Assert::commandExists git } - # @description intermediate callback that is used by Github::upgradeRelease # or Github::installRelease # if installCallback is not set, it allows to: @@ -610,7 +592,6 @@ Github::defaultInstall() { fi } - # @description github repository eg: kubernetes-sigs/kind # @arg $1 githubUrl:String eg: https://github.com/kubernetes-sigs/kind/releases/download/@latestVersion@/kind-linux-amd64 # @exitcode 1 if no matching repo found in provided url, 0 otherwise @@ -625,7 +606,6 @@ Github::extractRepoFromGithubUrl() { echo "${result}" } - # @description check if specified release software version exists in github # @arg $1 releaseUrl:String eg: https://github.com/kubernetes-sigs/kind/releases/download/v1.0.0/kind-linux-amd64 # @exitcode 1 on failure @@ -644,7 +624,6 @@ Github::isReleaseVersionExist() { "${releaseUrl}" } - # @description upgrade given binary to latest github release using retry # # downloadReleaseUrl argument : the placeholder @latestVersion@ will be replaced by the latest release version @@ -686,7 +665,6 @@ Github::upgradeRelease() { "${EXACT_VERSION:-}" } - # @description ensure command jq is available # @exitcode 1 if jq command not available # @stderr diagnostics information is displayed @@ -698,7 +676,6 @@ Linux::requireJqCommand() { fi } - # @description ensure command tar is available # @exitcode 1 if tar command not available # @stderr diagnostics information is displayed @@ -706,7 +683,6 @@ 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}" @@ -745,7 +721,6 @@ Log::computeDuration() { fi } - # @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 @@ -758,7 +733,6 @@ Log::displayDebug() { Log::logDebug "$1" } - # @description Display message using error color (red) # @arg $1 message:String the message to display # @env DISPLAY_DURATION int (default 0) if 1 display elapsed time information between 2 info logs @@ -771,7 +745,6 @@ Log::displayError() { Log::logError "$1" } - # @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 @@ -785,7 +758,6 @@ Log::displayInfo() { Log::logInfo "$1" "${type}" } - # @description Display message using skip 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 @@ -798,7 +770,6 @@ Log::displaySkipped() { Log::logSkipped "$1" } - # @description Display message using success color (bg green/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 @@ -811,7 +782,6 @@ Log::displaySuccess() { Log::logSuccess "$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 @@ -824,7 +794,6 @@ Log::displayWarning() { Log::logWarning "$1" } - # @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 @@ -836,7 +805,6 @@ Log::fatal() { exit 1 } - # @description log message to file # @arg $1 message:String the message to display Log::logDebug() { @@ -845,7 +813,6 @@ Log::logDebug() { fi } - # @description log message to file # @arg $1 message:String the message to display Log::logError() { @@ -854,14 +821,12 @@ Log::logError() { fi } - # @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() { @@ -870,7 +835,6 @@ Log::logInfo() { fi } - # @description Internal: common log message # @example text # [date]|[levelMsg]|message @@ -907,7 +871,6 @@ Log::logMessage() { fi } - # @description log message to file # @arg $1 message:String the message to display Log::logSkipped() { @@ -916,7 +879,6 @@ Log::logSkipped() { fi } - # @description log message to file # @arg $1 message:String the message to display Log::logSuccess() { @@ -925,7 +887,6 @@ Log::logSuccess() { fi } - # @description log message to file # @arg $1 message:String the message to display Log::logWarning() { @@ -934,7 +895,6 @@ Log::logWarning() { fi } - # @description activate or not Log::display* and Log::log* functions # based on BASH_FRAMEWORK_DISPLAY_LEVEL and BASH_FRAMEWORK_LOG_LEVEL # environment variables loaded by Env::requireLoad @@ -950,7 +910,6 @@ Log::logWarning() { Log::requireLoad() { 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 @@ -992,7 +951,6 @@ 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 @@ -1015,7 +973,6 @@ Log::rotate() { fi } - # @description Retry a command 5 times with a delay of 15 seconds between each attempt # @arg $@ command:String[] the command to run # @exitcode 0 on success @@ -1026,7 +983,6 @@ Retry::default() { Retry::parameterized "${RETRY_MAX_RETRY:-5}" "${RETRY_DELAY_BETWEEN_RETRIES:-15}" "" "$@" } - # @description Retry a command several times depending on parameters # @arg $1 maxRetries:int $1 max retries # @arg $2 delay:int between attempt @@ -1064,7 +1020,6 @@ Retry::parameterized() { return 0 } - # @description add reference to index file (using docsify embed feature) # @arg $1 indexFile:String # @arg $2 mdRelativeFile:String @@ -1080,7 +1035,6 @@ ShellDoc::appendDocToIndex() { ) >>"${indexFile}" } - # @description fix markdown TOC generated by Markdown all in one vscode extension # to make TOC compatible with docsify # @arg $1 file:String file to fix @@ -1089,19 +1043,18 @@ ShellDoc::appendDocToIndex() { ShellDoc::fixMarkdownToc() { local file="$1" -Linux::requireTarCommand -Compiler::Embed::extractFileFromBase64 \ - "${PERSISTENT_TMPDIR:-/tmp}/647e7865a1d35cf1435bb0aa74a5cdbe/fixMarkdownTocScript" \ - "ewogIGxpbmU9JDAKICBpZiAobWF0Y2gobGluZSwgL14oXHMqLSBcWyhbMC05XStcLikrIFteXV0rXF1cKCMpKFteKV0rKVwpLywgYXJyKSkgewogICAgcHJpbnQgYXJyWzFdICJfIiByZXdyaXRlKGFyclszXSkgIikiCiAgfSBlbHNlIHsKICAgIHByaW50IGxpbmUKICB9Cn0KZnVuY3Rpb24gcmV3cml0ZShzdHIpCnsKICAgIGdzdWIoLy0oLSkrLywgIi0iLCBzdHIpCiAgICByZXR1cm4gc3RyCn0K" \ - "644" + Linux::requireTarCommand + Compiler::Embed::extractFileFromBase64 \ + "${PERSISTENT_TMPDIR:-/tmp}/647e7865a1d35cf1435bb0aa74a5cdbe/fixMarkdownTocScript" \ + "ewogIGxpbmU9JDAKICBpZiAobWF0Y2gobGluZSwgL14oXHMqLSBcWyhbMC05XStcLikrIFteXV0rXF1cKCMpKFteKV0rKVwpLywgYXJyKSkgewogICAgcHJpbnQgYXJyWzFdICJfIiByZXdyaXRlKGFyclszXSkgIikiCiAgfSBlbHNlIHsKICAgIHByaW50IGxpbmUKICB9Cn0KZnVuY3Rpb24gcmV3cml0ZShzdHIpCnsKICAgIGdzdWIoLy0oLSkrLywgIi0iLCBzdHIpCiAgICByZXR1cm4gc3RyCn0K" \ + "644" -declare -gx embed_file_fixMarkdownTocScript="${PERSISTENT_TMPDIR:-/tmp}/647e7865a1d35cf1435bb0aa74a5cdbe/fixMarkdownTocScript" + declare -gx embed_file_fixMarkdownTocScript="${PERSISTENT_TMPDIR:-/tmp}/647e7865a1d35cf1435bb0aa74a5cdbe/fixMarkdownTocScript" # shellcheck disable=SC2154 awk -i inplace -f "${embed_file_fixMarkdownTocScript}" "${file}" } - # @description generates markdown file from template by # replacing @@@command_help@@@ by the help of the command # eg: @@@test_help@@@ will be replaced by the output @@ -1153,7 +1106,6 @@ ShellDoc::generateMdFileFromTemplate() { Log::displayInfo "${nbTokensGenerated} commands' help replaced in $(echo "scale=3; ${endTime} - ${startTime}" | bc)seconds" } - # @description extract shDoc from file # # @arg $1 file:String @@ -1170,7 +1122,6 @@ ShellDoc::generateShellDoc() { ) || true } - # @description generate shell doc file from given directory # # @arg $1 dir:String @@ -1247,7 +1198,6 @@ ShellDoc::generateShellDocDir() { ) } - # @description generate doc + index # @arg $1 fromDir:String # @arg $2 fromDirRelative:String @@ -1301,7 +1251,6 @@ ShellDoc::generateShellDocsFromDir() { done < <(cd "${fromDir}" && find . -type d -name '[^.]*' | "${grepExclude[@]}" | LC_ALL=C sort) } - BASH_FRAMEWORK_SHDOC_INSTALLED_PATH="vendor/.shDocInstalled" BASH_FRAMEWORK_SHDOC_CHECK_TIMEOUT=86400 # 1 day @@ -1335,7 +1284,6 @@ ShellDoc::installRequirementsIfNeeded() { fi } - # @description install hadolint if necessary # @arg $1 targetFile:String # @feature Github::upgradeRelease @@ -1346,7 +1294,6 @@ Softwares::installHadolint() { "https://github.com/hadolint/hadolint/releases/download/v@latestVersion@/hadolint-Linux-x86_64" } - # @description install hadolint if necessary # @arg $1 targetFile:String # @feature Github::upgradeRelease @@ -1372,7 +1319,6 @@ Softwares::installShellcheck() { "https://github.com/koalaman/shellcheck/releases/download/v@latestVersion@/shellcheck-v@latestVersion@.linux.x86_64.tar.xz" } - # @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() { @@ -1388,7 +1334,6 @@ UI::drawLine() { echo } - # @description load color theme # @noargs # @env BASH_FRAMEWORK_THEME String theme to use @@ -1402,7 +1347,6 @@ UI::requireTheme() { 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) @@ -1483,7 +1427,6 @@ UI::theme() { fi } - # @description extract software version number # @arg $1 command:String the command that will be called with --version parameter # @arg $2 argVersion:String allows to override default --version parameter @@ -1494,7 +1437,6 @@ Version::getCommandVersionFromPlainText() { Version::parse # keep only version numbers } - # @description extract version number from github api # @noargs # @stdin json result of github API @@ -1510,7 +1452,6 @@ Version::githubApiExtractVersion() { jq -r ".tag_name" } - # @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 @@ -1526,7 +1467,6 @@ Version::parse() { "$@" } - # @description Retrieve the latest version number of a web release # @arg $1 releaseListUrl:String the url from which version list can be retrieved # @stdout log messages about retry @@ -1542,7 +1482,6 @@ Web::getReleases() { "${releaseListUrl}" } - # @description upgrade given binary to latest release using retry # # releasesUrl argument : the placeholder @latestVersion@ will be replaced by the latest release version @@ -1608,7 +1547,6 @@ Web::upgradeRelease() { } # FUNCTIONS - declare -a BASH_FRAMEWORK_ARGV_FILTERED=() beforeParseCallback() { @@ -1854,8 +1792,6 @@ commandOptionParseFinished() { fi } - - # shellcheck disable=SC2034 declare optionContinuousIntegrationMode=0 @@ -1864,8 +1800,6 @@ updateOptionContinuousIntegrationMode() { BASH_FRAMEWORK_ARGV_FILTERED+=("$1") } - - # shellcheck disable=SC2034 declare copyrightBeginYear="2022" # shellcheck disable=SC2034 @@ -1878,7 +1812,6 @@ optionHelpCallback() { exit 0 } - # ------------------------------------------ # Command docCommand # ------------------------------------------ @@ -1948,7 +1881,6 @@ docCommandParse() { local -i options_parse_optionParsedCountOptionQuiet ((options_parse_optionParsedCountOptionQuiet = 0)) || true - # shellcheck disable=SC2034 local -i options_parse_parsedArgIndex=0 while (($# > 0)); do @@ -1962,7 +1894,7 @@ docCommandParse() { # shellcheck disable=SC2034 optionContinuousIntegrationMode="1" - if ((options_parse_optionParsedCountOptionContinuousIntegrationMode >= 1 )); then + if ((options_parse_optionParsedCountOptionContinuousIntegrationMode >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" return 1 fi @@ -1978,7 +1910,7 @@ docCommandParse() { # shellcheck disable=SC2034 optionHelp="1" - if ((options_parse_optionParsedCountOptionHelp >= 1 )); then + 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 @@ -1994,7 +1926,7 @@ docCommandParse() { # shellcheck disable=SC2034 optionConfig="1" - if ((options_parse_optionParsedCountOptionConfig >= 1 )); then + 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 @@ -2011,7 +1943,7 @@ docCommandParse() { return 1 fi - if ((options_parse_optionParsedCountOptionBashFrameworkConfig >= 1 )); then + 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 @@ -2029,7 +1961,7 @@ docCommandParse() { # shellcheck disable=SC2034 optionInfoVerbose="1" - if ((options_parse_optionParsedCountOptionInfoVerbose >= 1 )); then + 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 @@ -2047,7 +1979,7 @@ docCommandParse() { # shellcheck disable=SC2034 optionDebugVerbose="1" - if ((options_parse_optionParsedCountOptionDebugVerbose >= 1 )); then + 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 @@ -2065,7 +1997,7 @@ docCommandParse() { # shellcheck disable=SC2034 optionTraceVerbose="1" - if ((options_parse_optionParsedCountOptionTraceVerbose >= 1 )); then + 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 @@ -2091,7 +2023,7 @@ docCommandParse() { return 1 fi - if ((options_parse_optionParsedCountOptionLogLevel >= 1 )); then + 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 @@ -2114,7 +2046,7 @@ docCommandParse() { return 1 fi - if ((options_parse_optionParsedCountOptionLogFile >= 1 )); then + 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 @@ -2142,7 +2074,7 @@ docCommandParse() { return 1 fi - if ((options_parse_optionParsedCountOptionDisplayLevel >= 1 )); then + 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 @@ -2162,7 +2094,7 @@ docCommandParse() { # shellcheck disable=SC2034 optionNoColor="1" - if ((options_parse_optionParsedCountOptionNoColor >= 1 )); then + 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 @@ -2188,7 +2120,7 @@ docCommandParse() { return 1 fi - if ((options_parse_optionParsedCountOptionTheme >= 1 )); then + 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 @@ -2208,7 +2140,7 @@ docCommandParse() { # shellcheck disable=SC2034 optionVersion="1" - if ((options_parse_optionParsedCountOptionVersion >= 1 )); then + 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 @@ -2224,7 +2156,7 @@ docCommandParse() { # shellcheck disable=SC2034 optionQuiet="1" - if ((options_parse_optionParsedCountOptionQuiet >= 1 )); then + 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 @@ -2284,75 +2216,56 @@ docCommandHelp() { Array::wrap2 ' ' 76 4 " " "Activate continuous integration mode (tmp folder not shared with host)" "" echo - 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 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}--log-level 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" + Array::wrap2 ' ' 76 6 " Possible values: " "OFF, " "ERR, " "ERROR, " "WARN, " "WARNING, " "INFO, " "DEBUG, " "TRACE" echo - echo -e " ${__HELP_OPTION_COLOR}--log-file log-file${__HELP_NORMAL} {single}" Array::wrap2 ' ' 76 4 " " "Set log file" echo - - echo -e " ${__HELP_OPTION_COLOR}--display-level 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" + 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 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" + Array::wrap2 ' ' 76 6 " Possible values: " "default, " "default-force, " "noColor" echo Array::wrap2 ' ' 76 6 " Default value: " "default" @@ -2362,13 +2275,10 @@ docCommandHelp() { 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 - # ------------------------------------------ # longDescription section # ------------------------------------------ @@ -2376,7 +2286,7 @@ docCommandHelp() { echo echo -e "${__HELP_TITLE_COLOR}DESCRIPTION:${__RESET_COLOR}" declare -a docCommandLongDescription=( - "INTERNAL TOOL" + "INTERNAL TOOL" ) Array::wrap2 ' ' 76 0 "${docCommandLongDescription[@]}" echo @@ -2410,132 +2320,131 @@ docCommandHelp() { Array::wrap2 ' ' 76 0 "$(copyrightCallback)" } - beforeParseCallback docCommandParse "$@" MAIN_FUNCTION_NAME="main" main() { -COMMAND_BIN_DIR="${FRAMEWORK_ROOT_DIR}/bin" + COMMAND_BIN_DIR="${FRAMEWORK_ROOT_DIR}/bin" -runContainer() { - local image="scrasnups/build:bash-tools-ubuntu-5.3" - local -a dockerRunCmd=( - "/bash/bin/doc" - "${BASH_FRAMEWORK_ARGV_FILTERED[@]}" - ) + runContainer() { + local image="scrasnups/build:bash-tools-ubuntu-5.3" + local -a dockerRunCmd=( + "/bash/bin/doc" + "${BASH_FRAMEWORK_ARGV_FILTERED[@]}" + ) - if ! docker inspect --type=image "${image}" &>/dev/null; then - docker pull "${image}" - fi - # run docker image - local -a localDockerRunArgs=( - --rm - -e KEEP_TEMP_FILES="${KEEP_TEMP_FILES:-0}" - -e BATS_FIX_TEST="${BATS_FIX_TEST:-0}" - -w /bash - -v "${FRAMEWORK_ROOT_DIR}:/bash" - --entrypoint /usr/local/bin/bash - ) - # shellcheck disable=SC2154 - if [[ "${optionContinuousIntegrationMode}" = "0" ]]; then - localDockerRunArgs+=( - -e USER_ID="${USER_ID:-1000}" - -e GROUP_ID="${GROUP_ID:-1000}" - --user "www-data:www-data" - -v "/tmp:/tmp" + if ! docker inspect --type=image "${image}" &>/dev/null; then + docker pull "${image}" + fi + # run docker image + local -a localDockerRunArgs=( + --rm + -e KEEP_TEMP_FILES="${KEEP_TEMP_FILES:-0}" + -e BATS_FIX_TEST="${BATS_FIX_TEST:-0}" + -w /bash + -v "${FRAMEWORK_ROOT_DIR}:/bash" + --entrypoint /usr/local/bin/bash ) - fi + # shellcheck disable=SC2154 + if [[ "${optionContinuousIntegrationMode}" = "0" ]]; then + localDockerRunArgs+=( + -e USER_ID="${USER_ID:-1000}" + -e GROUP_ID="${GROUP_ID:-1000}" + --user "www-data:www-data" + -v "/tmp:/tmp" + ) + fi - # shellcheck disable=SC2154 - if [[ "${optionTraceVerbose}" = "1" ]]; then - set -x - fi - docker run \ - "${localDockerRunArgs[@]}" \ - "${image}" \ - "${dockerRunCmd[@]}" - set +x -} - -generateDoc() { - PAGES_DIR="${FRAMEWORK_ROOT_DIR}/pages" - export FRAMEWORK_ROOT_DIR - - #----------------------------- - # doc generation - #----------------------------- - - Log::displayInfo 'generate Commands.md' - ((TOKEN_NOT_FOUND_COUNT = 0)) || true - ShellDoc::generateMdFileFromTemplate \ - "${FRAMEWORK_ROOT_DIR}/doc/templates/Commands.tmpl.md" \ - "${PAGES_DIR}/Commands.md" \ - "${COMMAND_BIN_DIR}" \ - TOKEN_NOT_FOUND_COUNT \ - '(var|simpleBinary|shdoc|installFacadeExample)$' - - # clean folder before generate - rm -f "${PAGES_DIR}/Index.md" || true - rm -Rf "${PAGES_DIR}/bashDoc" || true - rm -Rf "${PAGES_DIR}/FrameworkIndex.md" || true - - ShellDoc::generateShellDocsFromDir \ - "${FRAMEWORK_SRC_DIR}" \ - "src" \ - "${PAGES_DIR}/bashDoc" \ - "${PAGES_DIR}/FrameworkIndex.md" \ - "<% ${REPOSITORY_URL} %>" \ - '/testsData|/_.*' \ - '(/__all\.sh)$' - cp "${FRAMEWORK_ROOT_DIR}/doc/guides/Docker.md" "${PAGES_DIR}/bashDoc/DockerUsage.md" - - cp "${FRAMEWORK_ROOT_DIR}/README.md" "${PAGES_DIR}" - sed -i -E \ - -e '//,//d' \ - -e 's#https://fchastanet.github.io/bash-tools-framework/#/#' \ - -e 's#^> \*\*_TIP:_\*\* (.*)$#> [!TIP|label:\1]#' \ - "${PAGES_DIR}/README.md" - - cp -R "${FRAMEWORK_ROOT_DIR}/doc" "${PAGES_DIR}" - rm -Rf "${PAGES_DIR}/doc/guides/templates" - - Log::displayInfo 'generate FrameworkFullDoc.md' - cp "${FRAMEWORK_ROOT_DIR}/doc/templates/FrameworkFullDoc.tmpl.md" "${PAGES_DIR}/FrameworkFullDoc.md" - ( - echo - find "${PAGES_DIR}/bashDoc" -type f -name '*.md' -print0 | LC_ALL=C sort -z | xargs -0 cat - ) >>"${PAGES_DIR}/FrameworkFullDoc.md" + # shellcheck disable=SC2154 + if [[ "${optionTraceVerbose}" = "1" ]]; then + set -x + fi + docker run \ + "${localDockerRunArgs[@]}" \ + "${image}" \ + "${dockerRunCmd[@]}" + set +x + } + + generateDoc() { + PAGES_DIR="${FRAMEWORK_ROOT_DIR}/pages" + export FRAMEWORK_ROOT_DIR + + #----------------------------- + # doc generation + #----------------------------- + + Log::displayInfo 'generate Commands.md' + ((TOKEN_NOT_FOUND_COUNT = 0)) || true + ShellDoc::generateMdFileFromTemplate \ + "${FRAMEWORK_ROOT_DIR}/doc/templates/Commands.tmpl.md" \ + "${PAGES_DIR}/Commands.md" \ + "${COMMAND_BIN_DIR}" \ + TOKEN_NOT_FOUND_COUNT \ + '(var|simpleBinary|shdoc|installFacadeExample)$' + + # clean folder before generate + rm -f "${PAGES_DIR}/Index.md" || true + rm -Rf "${PAGES_DIR}/bashDoc" || true + rm -Rf "${PAGES_DIR}/FrameworkIndex.md" || true + + ShellDoc::generateShellDocsFromDir \ + "${FRAMEWORK_SRC_DIR}" \ + "src" \ + "${PAGES_DIR}/bashDoc" \ + "${PAGES_DIR}/FrameworkIndex.md" \ + "<% ${REPOSITORY_URL} %>" \ + '/testsData|/_.*' \ + '(/__all\.sh)$' + cp "${FRAMEWORK_ROOT_DIR}/doc/guides/Docker.md" "${PAGES_DIR}/bashDoc/DockerUsage.md" + + cp "${FRAMEWORK_ROOT_DIR}/README.md" "${PAGES_DIR}" + sed -i -E \ + -e '//,//d' \ + -e 's#https://fchastanet.github.io/bash-tools-framework/#/#' \ + -e 's#^> \*\*_TIP:_\*\* (.*)$#> [!TIP|label:\1]#' \ + "${PAGES_DIR}/README.md" + + cp -R "${FRAMEWORK_ROOT_DIR}/doc" "${PAGES_DIR}" + rm -Rf "${PAGES_DIR}/doc/guides/templates" + + Log::displayInfo 'generate FrameworkFullDoc.md' + cp "${FRAMEWORK_ROOT_DIR}/doc/templates/FrameworkFullDoc.tmpl.md" "${PAGES_DIR}/FrameworkFullDoc.md" + ( + echo + find "${PAGES_DIR}/bashDoc" -type f -name '*.md' -print0 | LC_ALL=C sort -z | xargs -0 cat + ) >>"${PAGES_DIR}/FrameworkFullDoc.md" - while read -r path; do - ShellDoc::fixMarkdownToc "${path}" - done < <(find "${PAGES_DIR}" -type f -name '*.md') + while read -r path; do + ShellDoc::fixMarkdownToc "${path}" + done < <(find "${PAGES_DIR}" -type f -name '*.md') - if ((TOKEN_NOT_FOUND_COUNT > 0)); then - exit 1 - fi -} + if ((TOKEN_NOT_FOUND_COUNT > 0)); then + exit 1 + fi + } -installRequirements() { - Git::requireGitCommand -} + installRequirements() { + Git::requireGitCommand + } -installContainerRequirements() { - Git::requireGitCommand - ShellDoc::installRequirementsIfNeeded - Linux::requireJqCommand - Softwares::installHadolint - Softwares::installShellcheck -} + installContainerRequirements() { + Git::requireGitCommand + ShellDoc::installRequirementsIfNeeded + Linux::requireJqCommand + Softwares::installHadolint + Softwares::installShellcheck + } -if [[ "${IN_BASH_DOCKER:-}" = "You're in docker" ]]; then - installRequirements - generateDoc -else - installContainerRequirements - runContainer -fi + if [[ "${IN_BASH_DOCKER:-}" = "You're in docker" ]]; then + installRequirements + generateDoc + else + installContainerRequirements + runContainer + fi } diff --git a/src/Github/getLatestRelease.sh b/src/Github/getLatestRelease.sh index e9cf369c..0363bcbb 100755 --- a/src/Github/getLatestRelease.sh +++ b/src/Github/getLatestRelease.sh @@ -14,17 +14,31 @@ Github::getLatestRelease() { resultRef="" local resultFile resultFile="$(mktemp -p "${TMPDIR:-/tmp}" -t githubLatestRelease.XXXX)" - # Get latest release from GitHub api - if Retry::default curl \ - -L \ - --connect-timeout "${CURL_CONNECT_TIMEOUT:-5}" \ - -o "${resultFile}" \ - --fail \ - --silent \ - "https://api.github.com/repos/${repo}/releases/latest"; then - # shellcheck disable=SC2034 - resultRef="$(Version::githubApiExtractVersion <"${resultFile}")" - return 0 + local query="repos/${repo}/releases/latest" + + if command -v gh &>/dev/null && [[ -n "${GH_TOKEN}" ]]; then + Log::displayDebug "Using gh to retrieve release versions list" + Retry::parameterized "${RETRY_MAX_RETRY:-5}" \ + "${RETRY_DELAY_BETWEEN_RETRIES:-15}" \ + "Retrieving release versions list ..." \ + gh api "${query}" >"${resultFile}" + else + if command -v gh &>/dev/null && [[ "${GH_WARNING_DISPLAYED:-0}" = "0" ]]; then + Log::displayWarning "GH_TOKEN is not set, cannot use gh, using curl to retrieve release versions list" + GH_WARNING_DISPLAYED=1 + fi + # Get latest release from GitHub api + if Retry::default curl \ + -L \ + --connect-timeout "${CURL_CONNECT_TIMEOUT:-5}" \ + -o "${resultFile}" \ + --fail \ + --silent \ + "https://api.github.com/${query}"; then + # shellcheck disable=SC2034 + resultRef="$(Version::githubApiExtractVersion <"${resultFile}")" + return 0 + fi fi # display curl result in case of failure cat >&2 "${resultFile}" diff --git a/src/Github/installRelease.sh b/src/Github/installRelease.sh index fc091f9a..0b96c71c 100755 --- a/src/Github/installRelease.sh +++ b/src/Github/installRelease.sh @@ -11,6 +11,7 @@ # @arg $6 installCallback:Function called to install the file retrieved on github (default copy as is and set execution bit) # @stdout log messages about retry, install, upgrade # @env CURL_CONNECT_TIMEOUT number of seconds before giving up host connection +# @env VERSION_PLACEHOLDER a placeholder to replace in downloadReleaseUrl (default: @latestVersion@) Github::installRelease() { local targetFile="$1" local releaseUrl="$2" @@ -26,7 +27,10 @@ Github::installRelease() { fi if [[ "${currentVersion}" != "${exactVersion}" ]]; then Log::displayInfo "Installing ${targetFile} from version ${currentVersion} to ${exactVersion}" - url="$(echo "${releaseUrl}" | sed -E "s/@latestVersion@/${exactVersion}/g")" + url="$( + echo "${releaseUrl}" | + sed -E "s/${VERSION_PLACEHOLDER:-@latestVersion@}/${exactVersion}/g" + )" Log::displayInfo "Using url ${url}" newSoftware=$(mktemp -p "${TMPDIR:-/tmp}" -t github.newSoftware.XXXX) Retry::default curl \ diff --git a/src/Github/upgradeRelease.sh b/src/Github/upgradeRelease.sh index 8e6ca305..300ad4bc 100755 --- a/src/Github/upgradeRelease.sh +++ b/src/Github/upgradeRelease.sh @@ -33,6 +33,7 @@ Github::upgradeRelease() { } FILTER_LAST_VERSION_CALLBACK=${FILTER_LAST_VERSION_CALLBACK:-extractVersion} \ SOFT_VERSION_CALLBACK="${softVersionCallback}" \ + INSTALL_CALLBACK="${installCallback}" \ Web::upgradeRelease \ "${targetFile}" \ "${releasesUrl}" \ diff --git a/src/Web/getReleases.sh b/src/Web/getReleases.sh index 760a36f0..cae85da7 100755 --- a/src/Web/getReleases.sh +++ b/src/Web/getReleases.sh @@ -7,10 +7,25 @@ Web::getReleases() { local releaseListUrl="$1" # Get latest release from GitHub api - Retry::parameterized "${RETRY_MAX_RETRY:-5}" "${RETRY_DELAY_BETWEEN_RETRIES:-15}" "Retrieving release versions list ..." curl \ - -L \ - --connect-timeout "${CURL_CONNECT_TIMEOUT:-5}" \ - --fail \ - --silent \ - "${releaseListUrl}" + if command -v gh &>/dev/null && [[ -n "${GH_TOKEN}" ]]; then + Log::displayDebug "Using gh to retrieve release versions list" + Retry::parameterized "${RETRY_MAX_RETRY:-5}" \ + "${RETRY_DELAY_BETWEEN_RETRIES:-15}" \ + "Retrieving release versions list ..." \ + gh api "${releaseListUrl#https://github.com}" + else + if command -v gh &>/dev/null && [[ "${GH_WARNING_DISPLAYED:-0}" = "0" ]]; then + Log::displayWarning "GH_TOKEN is not set, cannot use gh, using curl to retrieve release versions list" + GH_WARNING_DISPLAYED=1 + fi + Retry::parameterized "${RETRY_MAX_RETRY:-5}" \ + "${RETRY_DELAY_BETWEEN_RETRIES:-15}" \ + "Retrieving release versions list ..." \ + curl \ + -L \ + --connect-timeout "${CURL_CONNECT_TIMEOUT:-5}" \ + --fail \ + --silent \ + "${releaseListUrl}" + fi } diff --git a/src/Web/upgradeRelease.sh b/src/Web/upgradeRelease.sh index 43fef5a3..80a2f569 100755 --- a/src/Web/upgradeRelease.sh +++ b/src/Web/upgradeRelease.sh @@ -14,6 +14,7 @@ # @env PARSE_VERSION_CALLBACK a callback to parse the version of the existing command # @env INSTALL_CALLBACK a callback to install the software downloaded # @env CURL_CONNECT_TIMEOUT number of seconds before giving up host connection +# @env VERSION_PLACEHOLDER a placeholder to replace in downloadReleaseUrl (default: @latestVersion@) Web::upgradeRelease() { local targetFile="$1" local releasesUrl="$2" @@ -24,25 +25,28 @@ Web::upgradeRelease() { local filterLastVersionCallback="${FILTER_LAST_VERSION_CALLBACK:-Version::parse}" local softVersionCallback="${SOFT_VERSION_CALLBACK:-Version::getCommandVersionFromPlainText}" local installCallback="${INSTALL_CALLBACK:-}" - local latestVersion - latestVersion="$(Web::getReleases "${releasesUrl}" | ${filterLastVersionCallback})" || { - Log::displayError "latest version not found on ${releasesUrl}" - return 1 - } - Log::displayInfo "Latest version found is ${latestVersion}" local currentVersion="not existing" if [[ -f "${targetFile}" ]]; then currentVersion="$(${softVersionCallback} "${targetFile}" "${softVersionArg}" 2>&1 || true)" fi if [[ -z "${exactVersion}" ]]; then + local latestVersion + latestVersion="$(Web::getReleases "${releasesUrl}" | ${filterLastVersionCallback})" || { + Log::displayError "latest version not found on ${releasesUrl}" + return 1 + } + Log::displayInfo "Latest version found is ${latestVersion}" + exactVersion="${latestVersion}" fi - local url="${downloadReleaseUrl//@latestVersion@/${exactVersion}}" + local url="${downloadReleaseUrl//${VERSION_PLACEHOLDER:-@latestVersion@}/${exactVersion}}" if [[ -n "${exactVersion}" ]] && ! Github::isReleaseVersionExist "${url}"; then Log::displayError "${targetFile} version ${exactVersion} doesn't exist on github" return 2 fi + Log::displayDebug "currentVersion: '${currentVersion}'" + Log::displayDebug "exactVersion: '${exactVersion}'" if [[ "${currentVersion}" = "${exactVersion}" ]]; then Log::displayInfo "${targetFile} version ${exactVersion} already installed" else