From 4a0641e19373dda2aa0cc516895a78cd12c7c644 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chastanet?= Date: Tue, 7 May 2024 23:07:12 +0200 Subject: [PATCH] shellcheckLint parallel processing ## shellcheckLint enhancements Added ability to shellcheck all git files in parallel using xargs with compatiility with json, json1, checkstyle formats ## Breaking changes - removed bin/test binary - removed bin/runBuildContainer - removed Docker::runBuildContainer - added GNU sed to alpine image - changed runUnitTests hook to rely on a test.sh file needed to be present in the target repository. ## Github workflow - refactored bin/doc - ensure installRequirements executed - removed uneeded steps for pre-commit - unit test generate checkstyle even if error - cspell - remove global dictionaries config key and adapt cspell rules accordingly - replaced technote-space/workflow-conclusion-action@v3 by AbsoLouie/workflow-conclusion-status@v1.0.2 --- .cspell/bash.txt | 1 + .cspell/codespellrc-dic.txt | 1 + .cspell/myAwk.txt | 8 + .cspell/readme.txt | 15 + .cspell/softwares.txt | 5 + .docker/Dockerfile.alpine | 4 +- .framework-config | 2 +- .github/preCommitGeneration.sh | 5 +- .github/workflows/docsify-gh-pages.yml | 2 +- .github/workflows/lint-test.yml | 37 +- .pre-commit-config-github.yaml | 7 +- .pre-commit-config.yaml | 7 +- .pre-commit-hooks.yaml | 4 +- .shellcheckrc | 2 +- README.md | 68 +- bin/doc | 912 +++----- bin/runBuildContainer | 1729 -------------- bin/shellcheckLint | 488 +--- bin/test | 2028 ----------------- cspell.yaml | 14 +- doc/templates/Commands.tmpl.md | 22 +- preCommitTest.sh | 8 + src/Dns/checkHostname.bats | 2 +- src/Dns/testsData/ifconfig.txt | 2 +- src/Docker/runBuildContainer.sh | 98 - src/_binaries/doc.sh | 78 +- src/_binaries/options/command.doc.tpl | 8 +- .../options/command.runBuildContainer.tpl | 47 - .../options/command.shellcheckLint.tpl | 6 +- src/_binaries/options/command.test.tpl | 155 -- src/_binaries/runBuildContainer.sh | 35 - src/_binaries/shellcheckLint.sh | 29 +- src/_binaries/test.sh | 52 - test.sh | 8 +- 34 files changed, 532 insertions(+), 5357 deletions(-) create mode 100644 .cspell/myAwk.txt delete mode 100755 bin/runBuildContainer delete mode 100755 bin/test create mode 100755 preCommitTest.sh delete mode 100755 src/Docker/runBuildContainer.sh delete mode 100644 src/_binaries/options/command.runBuildContainer.tpl delete mode 100644 src/_binaries/options/command.test.tpl delete mode 100755 src/_binaries/runBuildContainer.sh delete mode 100755 src/_binaries/test.sh diff --git a/.cspell/bash.txt b/.cspell/bash.txt index 031177dd..e6f59c0b 100644 --- a/.cspell/bash.txt +++ b/.cspell/bash.txt @@ -52,6 +52,7 @@ friday frégate getopt getsys +GKHF gpgsign gsub HISTFILE diff --git a/.cspell/codespellrc-dic.txt b/.cspell/codespellrc-dic.txt index d9ef9fd4..91e1ab2c 100644 --- a/.cspell/codespellrc-dic.txt +++ b/.cspell/codespellrc-dic.txt @@ -1,2 +1,3 @@ +adpat -> adapt fileTest -> fileTest tst -> test diff --git a/.cspell/myAwk.txt b/.cspell/myAwk.txt new file mode 100644 index 00000000..cac05b3e --- /dev/null +++ b/.cspell/myAwk.txt @@ -0,0 +1,8 @@ +apos +asort +gensub +getline +gsub +substr +tolower +varchar diff --git a/.cspell/readme.txt b/.cspell/readme.txt index 500063ca..192f3cff 100644 --- a/.cspell/readme.txt +++ b/.cspell/readme.txt @@ -11,11 +11,18 @@ bingitisancestorof bingitrenamebranch binmysql Brzóska +Chastanet cklmurl +elif +esac +exitcode fchastanet fplain +Inlines +inlines installfile Jetbrains +lastpipe libassertsh libutilssh loadprofile @@ -23,11 +30,19 @@ mandatorysoftware manpages markdownlint Michał +noargs openapi +posix Powerlevel powerlevel +readlink +realpath Scrasnups setupsh +shopt +Slatis +templating +TMPDIR Tratif unserialize untar diff --git a/.cspell/softwares.txt b/.cspell/softwares.txt index 2ae7de76..6225515a 100644 --- a/.cspell/softwares.txt +++ b/.cspell/softwares.txt @@ -96,10 +96,13 @@ MAILHOG mailserver mattrose mbstring +megalinter minikube Minikube mlocate Mlocate +Msys +MSYS MULTIPARTS mypy mysql @@ -129,6 +132,8 @@ resolvconf runc scrasnups screenlock +shellcheck +shellcheckrc SpacesAftertabs stylelint Sume diff --git a/.docker/Dockerfile.alpine b/.docker/Dockerfile.alpine index 86fc03d4..abbd8792 100644 --- a/.docker/Dockerfile.alpine +++ b/.docker/Dockerfile.alpine @@ -17,7 +17,9 @@ RUN \ "gettext" \ "git" \ "jq" \ - "parallel" + "parallel" \ + # GNU sed + "sed" # Create a default www-data user COPY createUser.alpine.sh /usr/local/bin/createUser.sh diff --git a/.framework-config b/.framework-config index fa780342..e38f54b5 100755 --- a/.framework-config +++ b/.framework-config @@ -25,7 +25,7 @@ fi # describe the functions that will be skipped from being imported FRAMEWORK_FUNCTIONS_IGNORE_REGEXP="${FRAMEWORK_FUNCTIONS_IGNORE_REGEXP:-^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|IMPORT::dir::file|Acquire::ForceIPv4)$}" # describe the files that do not contain function to be imported -NON_FRAMEWORK_FILES_REGEXP="${NON_FRAMEWORK_FILES_REGEXP:-(^bin/|^hooks/|^test.sh$|^.github/|^.docker/createUser.|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/_binaries|^src/_includes|^src/batsHeaders.sh$|^src/_standalone)}" +NON_FRAMEWORK_FILES_REGEXP="${NON_FRAMEWORK_FILES_REGEXP:-(^bin/|^hooks/|^test.sh$|^preCommitTest.sh$|^.github/|^.docker/createUser.|.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|^.docker/|^.github/|.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 diff --git a/.github/preCommitGeneration.sh b/.github/preCommitGeneration.sh index 3611ef01..175c8540 100755 --- a/.github/preCommitGeneration.sh +++ b/.github/preCommitGeneration.sh @@ -10,4 +10,7 @@ awk \ print "###############################################################################" } ''' .pre-commit-config.yaml >.pre-commit-config-github.yaml -sed -i -E '0,/fail_fast: true/s//fail_fast: false/' .pre-commit-config-github.yaml +sed -i -E \ + -e '0,/fail_fast: true/s//fail_fast: false/' \ + -e 's/stages: \[\] # GITHUB/stages: \[manual\] # GITHUB/' \ + .pre-commit-config-github.yaml diff --git a/.github/workflows/docsify-gh-pages.yml b/.github/workflows/docsify-gh-pages.yml index 970ca782..fa51a597 100644 --- a/.github/workflows/docsify-gh-pages.yml +++ b/.github/workflows/docsify-gh-pages.yml @@ -34,7 +34,7 @@ jobs: - name: Check if doc up to date run: | - ./bin/doc + ./bin/doc --ci - name: Setup Pages uses: actions/configure-pages@v4 diff --git a/.github/workflows/lint-test.yml b/.github/workflows/lint-test.yml index d3a9d73e..fffaa33a 100644 --- a/.github/workflows/lint-test.yml +++ b/.github/workflows/lint-test.yml @@ -199,26 +199,19 @@ jobs: echo "branch_name=${GITHUB_REF##*/}" ) >> "${GITHUB_ENV}" - - name: Cache pre-commit - uses: actions/cache@v4 - env: - cache_name: pre-commit - hash: ${{hashFiles('**/.pre-commit-config-github.yaml')}} - with: - path: ~/.cache/pre-commit - key: ${{ runner.os }}-${{ env.cache_name }}-${{ env.hash }} - restore-keys: ${{ runner.os }}-${{ env.cache_name }}- - - name: Set up Python uses: actions/setup-python@v5 with: python-version: 3.9 - - name: Install pre-commit - run: pip install pre-commit - - uses: fchastanet/github-action-setup-shfmt@v4.0.0 + - name: Install requirements + run: | + set -exo pipefail + + bin/installRequirements + - name: Run pre-commit uses: pre-commit/action@v3.0.1 id: preCommit @@ -382,13 +375,15 @@ jobs: - name: run unit tests id: unitTests run: | - set -x - set -o errexit - chmod -R 777 logs + set -exo pipefail bin/installRequirements + chmod 777 logs + docker pull "scrasnups/build:${{env.image_tag}}" + + status=0 docker run --rm \ -e KEEP_TEMP_FILES=0 \ -e BATS_FIX_TEST=0 \ @@ -401,15 +396,15 @@ jobs: /bash/vendor/bats/bin/bats \ ${{env.batsOptions}} \ --formatter junit -o logs -r src 2>&1 | - tee "logs/bats-${{ env.job_tag }}.log" || exit 1 + tee "logs/bats-${{ env.job_tag }}.log" || status=$? awk '/xml version="1.0"/{flag=1} flag; /<\/testsuites>/{flag=0}' \ "logs/bats-${{ env.job_tag }}.log" >"logs/junit-${{ env.job_tag }}.xml" + exit "${status}" - name: Publish Test Report uses: mikepenz/action-junit-report@v4 - # https://stackoverflow.com/a/70549615/3045926 - if: ${{ always() && steps.unitTests.conclusion == 'success' }} + if: ${{ always() }} with: token: ${{ github.token }} check_name: JUnit ${{ env.image_tag }} @@ -419,7 +414,7 @@ jobs: report_paths: "logs/**.xml" - name: Upload Test Results - if: ${{ always() && steps.unitTests.conclusion == 'success' }} + if: ${{ always() }} uses: actions/upload-artifact@v4 with: name: Test Results ${{ env.image_tag }} @@ -443,7 +438,7 @@ jobs: steps: # run this action to get the workflow conclusion # You can get the conclusion via env (env.WORKFLOW_CONCLUSION) - - uses: technote-space/workflow-conclusion-action@v3 + - uses: AbsoLouie/workflow-conclusion-status@v1.0.2 - uses: akatov/commit-status-updater@a9e988ec5454692ff7745a509452422a35172ad6 with: diff --git a/.pre-commit-config-github.yaml b/.pre-commit-config-github.yaml index bb08732c..b76a975c 100644 --- a/.pre-commit-config-github.yaml +++ b/.pre-commit-config-github.yaml @@ -155,7 +155,7 @@ repos: ) - repo: https://github.com/fchastanet/bash-tools-framework - rev: 3.2.0 + rev: 3.2.1 hooks: - id: fixShebangExecutionBit - id: awkLint @@ -214,8 +214,7 @@ repos: name: build doc files for github actions language: script entry: bin/doc - args: [--verbose] + args: [--verbose, --ci] pass_filenames: false require_serial: true - always_run: true - stages: [manual] + stages: [manual] # GITHUB diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 14b3f62c..0a1d1b96 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -150,7 +150,7 @@ repos: ) - repo: https://github.com/fchastanet/bash-tools-framework - rev: 3.2.0 + rev: 3.2.1 hooks: - id: fixShebangExecutionBit - id: awkLint @@ -209,8 +209,7 @@ repos: name: build doc files for github actions language: script entry: bin/doc - args: [--verbose] + args: [--verbose, --ci] pass_filenames: false require_serial: true - always_run: true - stages: [manual] + stages: [] # GITHUB diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index c3aeea8c..71f0e5e0 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -72,8 +72,8 @@ name: Run unit tests description: run unit test using bats in docker isolated environment language: script - entry: bin/test - args: [-r, src, -j, "30"] + entry: preCommitTest.sh + args: ["scrasnups/build:bash-tools-ubuntu-5.3", -r, src, -j, "30"] pass_filenames: false always_run: true require_serial: true diff --git a/.shellcheckrc b/.shellcheckrc index f192cf43..f7deaef5 100644 --- a/.shellcheckrc +++ b/.shellcheckrc @@ -6,4 +6,4 @@ enable=quote-safe-variables enable=require-double-brackets source-path=SCRIPTDIR # special property used by shellcheckLint -exclude=(^bin/bash-tpl$|\.bats$|\.tpl$|/testsData/) +exclude=(^bin/|\.bats$|\.tpl$|src/batsHeaders.sh$|/testsData/) diff --git a/README.md b/README.md index 19f14d81..a38bfff5 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,5 @@ # bash-tools-framework -- [1. Excerpt](#1-excerpt) - - [1.1. Compile command](#11-compile-command) - - [1.2. Build tools](#12-build-tools) - - [1.3. Internal tools](#13-internal-tools) -- [2. Framework](#2-framework) - - [2.1. Excerpt](#21-excerpt) - - [2.2. Usage](#22-usage) -- [3. Development Environment](#3-development-environment) - - [3.1. Install dev dependencies](#31-install-dev-dependencies) - - [3.2. Precommit hook](#32-precommit-hook) - - [3.3. UT](#33-ut) - - [3.3.1. Debug bats](#331-debug-bats) - - [3.4. connect to container manually](#34-connect-to-container-manually) - - [3.5. auto generated bash doc](#35-auto-generated-bash-doc) - - [3.6. github page](#36-github-page) -- [4. Troubleshooting](#4-troubleshooting) - - [4.1. compile.bats embed not working on alpine investigation](#41-compilebats-embed-not-working-on-alpine-investigation) -- [5. Acknowledgements](#5-acknowledgements) - > **_NOTE:_** Documentation is best viewed on @@ -36,7 +17,7 @@ -[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/dbelyaev/action-checkstyle?logo=github&sort=semver)](https://github.com/dbelyaev/action-checkstyle/releases) +[![GitHub release (latest SemVer)](https://img.shields.io/github/release/fchastanet/bash-tools-framework?logo=github&sort=semver)](https://github.com/fchastanet/bash-tools-framework/releases) [![GitHubLicense](https://img.shields.io/github/license/Naereen/StrapDown.js.svg)](https://github.com/fchastanet/bash-tools-framework/blob/master/LICENSE) [![CI/CD](https://github.com/fchastanet/bash-tools-framework/actions/workflows/lint-test.yml/badge.svg)](https://github.com/fchastanet/bash-tools-framework/actions?query=workflow%3A%22Lint+and+test%22+branch%3Amaster) [![ProjectStatus](http://opensource.box.com/badges/active.svg)](http://opensource.box.com/badges "Project Status") @@ -47,6 +28,25 @@ +- [1. Excerpt](#1-excerpt) + - [1.1. Compile command](#11-compile-command) + - [1.2. Build tools](#12-build-tools) + - [1.3. Internal tools](#13-internal-tools) +- [2. Framework](#2-framework) + - [2.1. Excerpt](#21-excerpt) + - [2.2. Usage](#22-usage) +- [3. Development Environment](#3-development-environment) + - [3.1. Install dev dependencies](#31-install-dev-dependencies) + - [3.2. Precommit hook](#32-precommit-hook) + - [3.3. UT](#33-ut) + - [3.3.1. Debug bats](#331-debug-bats) + - [3.4. connect to container manually](#34-connect-to-container-manually) + - [3.5. auto generated bash doc](#35-auto-generated-bash-doc) + - [3.6. github page](#36-github-page) +- [4. Troubleshooting](#4-troubleshooting) + - [4.1. compile.bats embed not working on alpine investigation](#41-compilebats-embed-not-working-on-alpine-investigation) +- [5. Acknowledgements](#5-acknowledgements) + ## 1. Excerpt [Full documentation can be found here](/#/FrameworkIndex) but here an excerpt of @@ -68,11 +68,9 @@ see related documentation [Compile command](doc/CompileCommand.md). ### 1.3. Internal tools -- **test** : test this framework by launching bats inside docker container with - the needed dependencies +- **test.sh** : test this framework by launching bats inside docker container + with the needed dependencies - **doc** : generate markdown documentation for this framework -- **runBuildContainer** : run docker container with the Dockerfile of this - project, allowing to build doc and run tests ## 2. Framework @@ -241,16 +239,21 @@ see related documentation [Compile command](doc/CompileCommand.md). Dependencies are automatically installed when used. -`bin/test` script will install the following libraries inside `vendor` folder: +`bin/installRequirements` script will install the following libraries inside +`vendor` folder: - [bats-core/bats-core](https://github.com/bats-core/bats-core.git) - [bats-core/bats-support](https://github.com/bats-core/bats-support.git) - [bats-core/bats-assert](https://github.com/bats-core/bats-assert.git) - [Flamefire/bats-mock](https://github.com/Flamefire/bats-mock.git) +- hadolint +- shellcheck `bin/doc` script will install: - [reconquest/shdoc](https://github.com/reconquest/shdoc) +- hadolint +- shellcheck To avoid checking for libraries update and have an impact on performance, a file is created in vendor dir. @@ -277,19 +280,18 @@ All the methods of this framework are unit tested, you can run the unit tests using the following command ```bash -./bin/test -r src +./test.sh scrasnups/build:bash-tools-ubuntu-5.3 -r src -j 30 ``` Launch UT on different environments: ```bash -VENDOR="alpine" BASH_TAR_VERSION=4.4 BASH_IMAGE=bash SKIP_BUILD=0 SKIP_USER=1 ./bin/test -r src -VENDOR="alpine" BASH_TAR_VERSION=5.0 BASH_IMAGE=bash SKIP_BUILD=0 SKIP_USER=1 ./bin/test -r src -VENDOR="alpine" BASH_TAR_VERSION=5.1 BASH_IMAGE=bash SKIP_BUILD=0 SKIP_USER=1 ./bin/test -r src - -VENDOR="ubuntu" BASH_TAR_VERSION=4.4 BASH_IMAGE=ubuntu:20.04 SKIP_BUILD=0 SKIP_USER=1 ./bin/test -r src -VENDOR="ubuntu" BASH_TAR_VERSION=5.0 BASH_IMAGE=ubuntu:20.04 SKIP_BUILD=0 SKIP_USER=1 ./bin/test -r src -VENDOR="ubuntu" BASH_TAR_VERSION=5.1 BASH_IMAGE=ubuntu:20.04 SKIP_BUILD=0 SKIP_USER=1 ./bin/test -r src +./test.sh scrasnups/build:bash-tools-ubuntu-4.4 -r src -j 30 +./test.sh scrasnups/build:bash-tools-ubuntu-5.0 -r src -j 30 +./test.sh scrasnups/build:bash-tools-ubuntu-5.3 -r src -j 30 +./test.sh scrasnups/build:bash-tools-alpine-4.4 -r src -j 30 +./test.sh scrasnups/build:bash-tools-alpine-5.0 -r src -j 30 +./test.sh scrasnups/build:bash-tools-alpine-5.3 -r src -j 30 ``` #### 3.3.1. Debug bats diff --git a/bin/doc b/bin/doc index bbf5c4da..e9115675 100755 --- a/bin/doc +++ b/bin/doc @@ -502,175 +502,6 @@ Array::wrap2() { ) | sed -E -e 's/[[:blank:]]+$//' } -BASH_FRAMEWORK_SHDOC_INSTALLED_PATH="vendor/.shDocInstalled" -BASH_FRAMEWORK_SHDOC_CHECK_TIMEOUT=86400 # 1 day - -# @description install requirements to execute shdoc -# @warning cloning is skipped if vendor/.shDocInstalled file exists -# @warning a new check is done everyday -# @warning repository is not updated if a change is detected -# @env BASH_FRAMEWORK_SHDOC_CHECK_TIMEOUT int default value 86400 (86400 seconds = 1 day) -# @set BASH_FRAMEWORK_SHDOC_INSTALLED String the file created when git clone succeeded -# @see https://github.com/fchastanet/shdoc -# @stderr diagnostics information is displayed -# @feature Git::cloneOrPullIfNoChanges -ShellDoc::installRequirementsIfNeeded() { - local BASH_FRAMEWORK_SHDOC_INSTALLED="${FRAMEWORK_ROOT_DIR}/${BASH_FRAMEWORK_SHDOC_INSTALLED_PATH}" - if [[ -d "${FRAMEWORK_ROOT_DIR}/vendor" ]]; then - mkdir -p "${FRAMEWORK_ROOT_DIR}/vendor" || return 1 - fi - if [[ "$( - Cache::getFileContentIfNotExpired \ - "${BASH_FRAMEWORK_SHDOC_INSTALLED}" \ - "${BASH_FRAMEWORK_SHDOC_CHECK_TIMEOUT}" - )" != "1" ]]; then - Log::displayInfo "Check if shdoc is up to date" - if GIT_CLONE_OPTIONS="--recursive" Git::cloneOrPullIfNoChanges \ - "${FRAMEWORK_VENDOR_DIR:-${FRAMEWORK_ROOT_DIR}/vendor}/shdoc" \ - "https://github.com/fchastanet/shdoc.git"; then - echo "1" >"${BASH_FRAMEWORK_SHDOC_INSTALLED}" - else - Log::fatal "unable to install shdoc library" - fi - fi -} - -# @description install hadolint if necessary -# @arg $1 targetFile:String -# @feature Github::upgradeRelease -Softwares::installHadolint() { - local targetFile="${1:-${FRAMEWORK_VENDOR_BIN_DIR}/hadolint}" - Github::upgradeRelease \ - "${targetFile}" \ - "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 -Softwares::installShellcheck() { - local targetFile="${1:-${FRAMEWORK_VENDOR_BIN_DIR}/shellcheck}" - # shellcheck disable=SC2317 - install() { - local file="$1" - local targetFile="$2" - local version="$3" - local tempDir - tempDir="$(mktemp -d -p "${TMPDIR:-/tmp}" -t bash-framework-shellcheck-$$-XXXXXX)" - ( - cd "${tempDir}" || exit 1 - tar -xJvf "${file}" >&2 - mv "shellcheck-v${version}/shellcheck" "${targetFile}" - chmod +x "${targetFile}" - rm -f "${file}" || true - ) - } - INSTALL_CALLBACK=install Github::upgradeRelease \ - "${targetFile}" \ - "https://github.com/koalaman/shellcheck/releases/download/v@latestVersion@/shellcheck-v@latestVersion@.linux.x86_64.tar.xz" -} - -# @description checkout usage doc below -# -# [DockerNamespace usage](DockerUsage.md ':include') - -# @description run the container specified by args provided. -# build and push the image if needed -# -# @env DOCKER_BUILD_OPTIONS -# @env SKIP_USER -# @env USER_ID -# @env GROUP_ID -# @env FRAMEWORK_ROOT_DIR -# @env DOCKER_OPTION_IMAGE_TAG -# @env BASH_FRAMEWORK_ARGV_FILTERED -Docker::runBuildContainer() { - local optionVendor="$1" - local optionBashVersion="$2" - local optionBashBaseImage="$3" - local optionSkipDockerBuild="$4" - local optionTraceVerbose="$5" - local optionContinuousIntegrationMode="$6" - local -n localDockerRunCmd=$7 - local -n localDockerRunArgs=$8 - - if [[ -d "$(pwd)/vendor/bash-tools-framework" ]]; then - localDockerRunArgs+=( - -v "$(cd "$(pwd)/vendor/bash-tools-framework" && pwd -P):/bash/vendor/bash-tools-framework" - ) - fi - - # shellcheck disable=SC2154 - if [[ "${optionContinuousIntegrationMode}" = "0" ]]; then - localDockerRunArgs+=(-v "/tmp:/tmp") - fi - localDockerRunArgs+=(-e KEEP_TEMP_FILES="${KEEP_TEMP_FILES}") - localDockerRunArgs+=(-e BATS_FIX_TEST="${BATS_FIX_TEST:-0}") - - # shellcheck disable=SC2154 - Log::displayInfo "Using ${optionVendor}:${optionBashVersion}" - - local imageRef="${DOCKER_OPTION_IMAGE_TAG:-build:bash-tools-${optionVendor}-${optionBashVersion}}" - if [[ "${optionSkipDockerBuild:-0}" != "1" ]]; then - Log::displayInfo "Build docker image ${imageRef}" - # shellcheck disable=SC2154 - ( - if [[ "${optionTraceVerbose}" = "1" ]]; then - set -x - fi - Docker::buildPushDockerImage \ - "${optionVendor}" \ - "${optionBashVersion}" \ - "${optionBashBaseImage}" \ - "${optionPush}" \ - "${optionTraceVerbose}" - ) - fi - if [[ -f "${FRAMEWORK_ROOT_DIR}/.docker/DockerfileUser" ]]; then - local imageRefUser="${imageRef}-user" - if [[ "${optionSkipDockerBuild:-0}" != "1" ]]; then - Log::displayInfo "build docker image ${imageRefUser} with user configuration" - # shellcheck disable=SC2154 - ( - if [[ "${optionTraceVerbose}" = "1" ]]; then - set -x - fi - # shellcheck disable=SC2086 - DOCKER_BUILDKIT=1 docker build \ - ${DOCKER_BUILD_OPTIONS} \ - --cache-from "scrasnups/${imageRef}" \ - --build-arg "BASH_IMAGE=scrasnups/${imageRef}" \ - --build-arg SKIP_USER="${SKIP_USER:-0}" \ - --build-arg USER_ID="${USER_ID:-$(id -u)}" \ - --build-arg GROUP_ID="${GROUP_ID:-$(id -g)}" \ - -f "${FRAMEWORK_ROOT_DIR}/.docker/DockerfileUser" \ - -t "${imageRefUser}" \ - "${FRAMEWORK_ROOT_DIR}/.docker" - ) - fi - fi - - Log::displayDebug "Run container with localDockerRunCmd: ${localDockerRunCmd[*]}" - Log::displayDebug "Run container with localDockerRunArgs: ${localDockerRunArgs[*]}" - Log::displayDebug "Run container with BASH_FRAMEWORK_ARGV_FILTERED: ${BASH_FRAMEWORK_ARGV_FILTERED[*]}" - ( - # shellcheck disable=SC2154 - if [[ "${optionTraceVerbose}" = "1" ]]; then - set -x - fi - # shellcheck disable=SC2086 - docker run \ - --rm \ - "${localDockerRunArgs[@]}" \ - ${DOCKER_RUN_OPTIONS} \ - -w /bash \ - -v "$(pwd):/bash" \ - --user "${USER_ID:-$(id -u)}:${GROUP_ID:-$(id -g)}" \ - "${imageRefUser}" \ - "${localDockerRunCmd[@]}" - ) -} - # @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 @@ -805,6 +636,74 @@ EOF awk -i inplace "${fixMarkdownToc}" "${file}" } +BASH_FRAMEWORK_SHDOC_INSTALLED_PATH="vendor/.shDocInstalled" +BASH_FRAMEWORK_SHDOC_CHECK_TIMEOUT=86400 # 1 day + +# @description install requirements to execute shdoc +# @warning cloning is skipped if vendor/.shDocInstalled file exists +# @warning a new check is done everyday +# @warning repository is not updated if a change is detected +# @env BASH_FRAMEWORK_SHDOC_CHECK_TIMEOUT int default value 86400 (86400 seconds = 1 day) +# @set BASH_FRAMEWORK_SHDOC_INSTALLED String the file created when git clone succeeded +# @see https://github.com/fchastanet/shdoc +# @stderr diagnostics information is displayed +# @feature Git::cloneOrPullIfNoChanges +ShellDoc::installRequirementsIfNeeded() { + local BASH_FRAMEWORK_SHDOC_INSTALLED="${FRAMEWORK_ROOT_DIR}/${BASH_FRAMEWORK_SHDOC_INSTALLED_PATH}" + if [[ -d "${FRAMEWORK_ROOT_DIR}/vendor" ]]; then + mkdir -p "${FRAMEWORK_ROOT_DIR}/vendor" || return 1 + fi + if [[ "$( + Cache::getFileContentIfNotExpired \ + "${BASH_FRAMEWORK_SHDOC_INSTALLED}" \ + "${BASH_FRAMEWORK_SHDOC_CHECK_TIMEOUT}" + )" != "1" ]]; then + Log::displayInfo "Check if shdoc is up to date" + if GIT_CLONE_OPTIONS="--recursive" Git::cloneOrPullIfNoChanges \ + "${FRAMEWORK_VENDOR_DIR:-${FRAMEWORK_ROOT_DIR}/vendor}/shdoc" \ + "https://github.com/fchastanet/shdoc.git"; then + echo "1" >"${BASH_FRAMEWORK_SHDOC_INSTALLED}" + else + Log::fatal "unable to install shdoc library" + fi + fi +} + +# @description install hadolint if necessary +# @arg $1 targetFile:String +# @feature Github::upgradeRelease +Softwares::installHadolint() { + local targetFile="${1:-${FRAMEWORK_VENDOR_BIN_DIR}/hadolint}" + Github::upgradeRelease \ + "${targetFile}" \ + "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 +Softwares::installShellcheck() { + local targetFile="${1:-${FRAMEWORK_VENDOR_BIN_DIR}/shellcheck}" + # shellcheck disable=SC2317 + install() { + local file="$1" + local targetFile="$2" + local version="$3" + local tempDir + tempDir="$(mktemp -d -p "${TMPDIR:-/tmp}" -t bash-framework-shellcheck-$$-XXXXXX)" + ( + cd "${tempDir}" || exit 1 + tar -xJvf "${file}" >&2 + mv "shellcheck-v${version}/shellcheck" "${targetFile}" + chmod +x "${targetFile}" + rm -f "${file}" || true + ) + } + INSTALL_CALLBACK=install Github::upgradeRelease \ + "${targetFile}" \ + "https://github.com/koalaman/shellcheck/releases/download/v@latestVersion@/shellcheck-v@latestVersion@.linux.x86_64.tar.xz" +} + # @description ensure COMMAND_BIN_DIR env var is set # and PATH correctly prepared # @noargs @@ -955,173 +854,6 @@ Log::rotate() { fi } -# @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 -# @stdout {String} the file content if not expired -# @exitcode 1 if file does not exists -# @exitcode 2 if file expired -Cache::getFileContentIfNotExpired() { - local file="$1" - local maxDuration="$2" - - if [[ ! -f "${file}" ]]; then - return 1 - fi - if (($(File::elapsedTimeSinceLastModification "${file}") > maxDuration)); then - return 2 - fi - cat "${file}" -} - -# @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 -# @arg $3 cloneCallback:Function callback on successful clone -# @arg $4 pullCallback:Function callback on successful pull -# @env GIT_CLONE_OPTIONS:String additional options to pass to git clone command -# @env SUDO String allows to use custom sudo prefix command -# @exitcode 0 on successful pulling/cloning, 1 on failure -Git::cloneOrPullIfNoChanges() { - local dir="$1" - shift || true - local repo="$1" - shift || true - local cloneCallback=${1:-} - shift || true - local pullCallback=${1:-} - shift || true - - if [[ -d "${dir}/.git" ]]; then - local exitCode=0 - Git::pullIfNoChanges "${dir}" || exitCode=$? - if Array::contains "${exitCode}" "2" "4"; then - # changes detected - return 0 - fi - if [[ "${exitCode}" != "0" ]]; then - return "${exitCode}" - fi - # shellcheck disable=SC2086 - if [[ "$(type -t ${pullCallback})" = "function" ]]; then - ${pullCallback} "${dir}" - fi - else - Log::displayInfo "cloning ${repo} ..." - if ! ${SUDO:-} test -d "${dir%/*}"; then - ${SUDO:-} mkdir -p "${dir%/*}" - fi - # shellcheck disable=SC2086,SC2248 - if ${SUDO:-} git clone ${GIT_CLONE_OPTIONS} --progress "$@" "${repo}" "${dir}"; then - # shellcheck disable=SC2086 - if [[ "$(type -t ${cloneCallback})" = "function" ]]; then - ${cloneCallback} "${dir}" - fi - else - Log::displayError "Cloning '${repo}' on '${dir}' failed" - return 1 - fi - fi -} - -# @description upgrade given binary to latest github release using retry -# -# downloadReleaseUrl argument : the placeholder @latestVersion@ will be replaced by the latest release version -# @arg $1 targetFile:String target binary file (eg: /usr/local/bin/kind) -# @arg $2 downloadReleaseUrl:String github release url (eg: https://github.com/kubernetes-sigs/kind/releases/download/@latestVersion@/kind-linux-amd64) -# @arg $3 softVersionArg:String parameter to add to existing command to compute current version -# @arg $4 softVersionCallback:Function function called to get software version (default: Version::getCommandVersionFromPlainText will call software with argument --version) -# @arg $5 installCallback:Function function called to install the file retrieved on github (default copy as is and set execution bit) -# @arg $6 softVersionCallback:Function function to call to filter the version retrieved from github (Default: Version::parse) -# @stdout log messages about retry, install, upgrade -# @env SOFT_VERSION_CALLBACK pass softVersionCallback by env variable instead of passing it by arg -# @env INSTALL_CALLBACK pass installCallback by env variable instead of passing it by arg -# @env CURL_CONNECT_TIMEOUT number of seconds before giving up host connection -# @env EXACT_VERSION if provided, retrieve exact version instead of the latest -Github::upgradeRelease() { - local targetFile="$1" - local downloadReleaseUrl="$2" - local softVersionArg="${3:---version}" - local softVersionCallback="${4:-${SOFT_VERSION_CALLBACK:-Version::getCommandVersionFromPlainText}}" - # shellcheck disable=SC2034 - local installCallback="${5:-${INSTALL_CALLBACK:-}}" - local parseGithubVersionCallback="${6:-${PARSE_VERSION_CALLBACK:-Version::parse}}" - - local repo - repo="$(Github::extractRepoFromGithubUrl "${downloadReleaseUrl}")" - local releasesUrl="https://api.github.com/repos/${repo}/releases/latest" - - # shellcheck disable=SC2317 - extractVersion() { - Version::githubApiExtractVersion | "${parseGithubVersionCallback}" - } - FILTER_LAST_VERSION_CALLBACK=${FILTER_LAST_VERSION_CALLBACK:-extractVersion} \ - SOFT_VERSION_CALLBACK="${softVersionCallback}" \ - Web::upgradeRelease \ - "${targetFile}" \ - "${releasesUrl}" \ - "${downloadReleaseUrl}" \ - "${softVersionArg}" \ - "${EXACT_VERSION:-}" -} - -# @description build image and push it to registry -# @env DOCKER_OPTION_IMAGE_TAG String computed from optionVendor and optionBashVersion if not provided -# @env DOCKER_OPTION_IMAGE String default scrasnups/${DOCKER_OPTION_IMAGE_TAG} -# @env DOCKER_BUILD_OPTIONS String list of docker arguments to pass to docker build command -# @env FRAMEWORK_ROOT_DIR String path allowing to deduce .docker/Dockerfile.{vendor} -Docker::buildPushDockerImage() { - local optionVendor="$1" - local optionBashVersion="$2" - local optionBashBaseImage="$3" - local optionPush="$4" - local optionTraceVerbose="$5" - # parameters based on env variables - local imageTag="${DOCKER_OPTION_IMAGE_TAG:-build:bash-tools-${optionVendor}-${optionBashVersion}}" - local image="${DOCKER_OPTION_IMAGE:-scrasnups/${imageTag}}" - local DOCKER_BUILD_OPTIONS="${DOCKER_BUILD_OPTIONS:-}" - - Log::displayInfo "Pull image ${image}" - ( - if [[ "${optionTraceVerbose}" = "1" ]]; then - set -x - fi - docker pull "${image}" || true - ) - - Log::displayInfo "Build image ${image}" - # shellcheck disable=SC2086 - ( - if [[ "${optionTraceVerbose}" = "1" ]]; then - set -x - fi - DOCKER_BUILDKIT=1 docker build \ - ${DOCKER_BUILD_OPTIONS} \ - -f "${FRAMEWORK_ROOT_DIR}/.docker/Dockerfile.${optionVendor}" \ - --cache-from "${image}" \ - --build-arg BUILDKIT_INLINE_CACHE=1 \ - --build-arg argBashVersion="${optionBashVersion}" \ - --build-arg BASH_IMAGE="${optionBashBaseImage}" \ - -t "${imageTag}" \ - -t "${image}" \ - "${FRAMEWORK_ROOT_DIR}/.docker" - ) - - Log::displayInfo "Image ${image} - bash version check" - docker run --rm "${imageTag}" bash --version - - # shellcheck disable=SC2154 - if [[ "${optionPush}" = "1" ]]; then - Log::displayInfo "Push image ${image}" - ( - if [[ "${optionTraceVerbose}" = "1" ]]; then - set -x - fi - docker push "scrasnups/${imageTag}" - ) - fi -} - # @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 @@ -1220,34 +952,144 @@ ShellDoc::generateShellDocDir() { else return 1 fi - ) + ) +} + +# @description add reference to index file (using docsify embed feature) +# @arg $1 indexFile:String +# @arg $2 mdRelativeFile:String +# @arg $3 title:String +ShellDoc::appendDocToIndex() { + local indexFile="$1" + local mdRelativeFile="$2" + local title="$3" + + ( + echo "[${title}](${mdRelativeFile} ':include')" + echo + ) >>"${indexFile}" +} + +# @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 +# @env LOG_CONTEXT String allows to contextualize the log +Log::displaySkipped() { + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_INFO)); then + Log::computeDuration + echo -e "${__SKIPPED_COLOR}SKIPPED - ${LOG_CONTEXT:-}${LOG_LAST_DURATION_STR:-}${1}${__RESET_COLOR}" >&2 + fi + Log::logSkipped "$1" +} + +# @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 +# @stdout {String} the file content if not expired +# @exitcode 1 if file does not exists +# @exitcode 2 if file expired +Cache::getFileContentIfNotExpired() { + local file="$1" + local maxDuration="$2" + + if [[ ! -f "${file}" ]]; then + return 1 + fi + if (($(File::elapsedTimeSinceLastModification "${file}") > maxDuration)); then + return 2 + fi + cat "${file}" +} + +# @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 +# @arg $3 cloneCallback:Function callback on successful clone +# @arg $4 pullCallback:Function callback on successful pull +# @env GIT_CLONE_OPTIONS:String additional options to pass to git clone command +# @env SUDO String allows to use custom sudo prefix command +# @exitcode 0 on successful pulling/cloning, 1 on failure +Git::cloneOrPullIfNoChanges() { + local dir="$1" + shift || true + local repo="$1" + shift || true + local cloneCallback=${1:-} + shift || true + local pullCallback=${1:-} + shift || true + + if [[ -d "${dir}/.git" ]]; then + local exitCode=0 + Git::pullIfNoChanges "${dir}" || exitCode=$? + if Array::contains "${exitCode}" "2" "4"; then + # changes detected + return 0 + fi + if [[ "${exitCode}" != "0" ]]; then + return "${exitCode}" + fi + # shellcheck disable=SC2086 + if [[ "$(type -t ${pullCallback})" = "function" ]]; then + ${pullCallback} "${dir}" + fi + else + Log::displayInfo "cloning ${repo} ..." + if ! ${SUDO:-} test -d "${dir%/*}"; then + ${SUDO:-} mkdir -p "${dir%/*}" + fi + # shellcheck disable=SC2086,SC2248 + if ${SUDO:-} git clone ${GIT_CLONE_OPTIONS} --progress "$@" "${repo}" "${dir}"; then + # shellcheck disable=SC2086 + if [[ "$(type -t ${cloneCallback})" = "function" ]]; then + ${cloneCallback} "${dir}" + fi + else + Log::displayError "Cloning '${repo}' on '${dir}' failed" + return 1 + fi + fi } -# @description add reference to index file (using docsify embed feature) -# @arg $1 indexFile:String -# @arg $2 mdRelativeFile:String -# @arg $3 title:String -ShellDoc::appendDocToIndex() { - local indexFile="$1" - local mdRelativeFile="$2" - local title="$3" +# @description upgrade given binary to latest github release using retry +# +# downloadReleaseUrl argument : the placeholder @latestVersion@ will be replaced by the latest release version +# @arg $1 targetFile:String target binary file (eg: /usr/local/bin/kind) +# @arg $2 downloadReleaseUrl:String github release url (eg: https://github.com/kubernetes-sigs/kind/releases/download/@latestVersion@/kind-linux-amd64) +# @arg $3 softVersionArg:String parameter to add to existing command to compute current version +# @arg $4 softVersionCallback:Function function called to get software version (default: Version::getCommandVersionFromPlainText will call software with argument --version) +# @arg $5 installCallback:Function function called to install the file retrieved on github (default copy as is and set execution bit) +# @arg $6 softVersionCallback:Function function to call to filter the version retrieved from github (Default: Version::parse) +# @stdout log messages about retry, install, upgrade +# @env SOFT_VERSION_CALLBACK pass softVersionCallback by env variable instead of passing it by arg +# @env INSTALL_CALLBACK pass installCallback by env variable instead of passing it by arg +# @env CURL_CONNECT_TIMEOUT number of seconds before giving up host connection +# @env EXACT_VERSION if provided, retrieve exact version instead of the latest +Github::upgradeRelease() { + local targetFile="$1" + local downloadReleaseUrl="$2" + local softVersionArg="${3:---version}" + local softVersionCallback="${4:-${SOFT_VERSION_CALLBACK:-Version::getCommandVersionFromPlainText}}" + # shellcheck disable=SC2034 + local installCallback="${5:-${INSTALL_CALLBACK:-}}" + local parseGithubVersionCallback="${6:-${PARSE_VERSION_CALLBACK:-Version::parse}}" - ( - echo "[${title}](${mdRelativeFile} ':include')" - echo - ) >>"${indexFile}" -} + local repo + repo="$(Github::extractRepoFromGithubUrl "${downloadReleaseUrl}")" + local releasesUrl="https://api.github.com/repos/${repo}/releases/latest" -# @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 -# @env LOG_CONTEXT String allows to contextualize the log -Log::displaySkipped() { - if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_INFO)); then - Log::computeDuration - echo -e "${__SKIPPED_COLOR}SKIPPED - ${LOG_CONTEXT:-}${LOG_LAST_DURATION_STR:-}${1}${__RESET_COLOR}" >&2 - fi - Log::logSkipped "$1" + # shellcheck disable=SC2317 + extractVersion() { + Version::githubApiExtractVersion | "${parseGithubVersionCallback}" + } + FILTER_LAST_VERSION_CALLBACK=${FILTER_LAST_VERSION_CALLBACK:-extractVersion} \ + SOFT_VERSION_CALLBACK="${softVersionCallback}" \ + Web::upgradeRelease \ + "${targetFile}" \ + "${releasesUrl}" \ + "${downloadReleaseUrl}" \ + "${softVersionArg}" \ + "${EXACT_VERSION:-}" } # @description prepend directories to the PATH environment variable @@ -1273,6 +1115,43 @@ UI::requireTheme() { fi } +# @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 +# @stdin you can use stdin as alternative to files argument +# @stdout the filtered content +# @see https://en.wikipedia.org/wiki/ANSI_escape_code +# shellcheck disable=SC2120 +Filters::removeAnsiCodes() { + # cspell:disable + sed -E 's/\x1b\[[0-9;]*[mGKHF]//g' "$@" + # cspell:enable +} + +# @description extract shDoc from file +# +# @arg $1 file:String +# @stdout the shell documentation in markdown format +ShellDoc::generateShellDoc() { + local file="$1" + + ShellDoc::installRequirementsIfNeeded + ( + "${FRAMEWORK_VENDOR_DIR}/shdoc/shdoc" <"${file}" || { + Log::displayError "parse error of file ${file}" + return 0 + } + ) || true +} + +# @description log message to file +# @arg $1 message:String the message to display +Log::logSkipped() { + if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_INFO)); then + Log::logMessage "${2:-SKIPPED}" "$1" + fi +} + # @description get number of seconds since last modification of the file # @arg $1 file:String file path # @exitcode 1 if file does not exist @@ -1447,43 +1326,6 @@ Web::upgradeRelease() { fi } -# @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 -# @stdin you can use stdin as alternative to files argument -# @stdout the filtered content -# @see https://en.wikipedia.org/wiki/ANSI_escape_code -# shellcheck disable=SC2120 -Filters::removeAnsiCodes() { - # cspell:disable - sed -E 's/\x1b\[[0-9;]*[mGKHF]//g' "$@" - # cspell:enable -} - -# @description extract shDoc from file -# -# @arg $1 file:String -# @stdout the shell documentation in markdown format -ShellDoc::generateShellDoc() { - local file="$1" - - ShellDoc::installRequirementsIfNeeded - ( - "${FRAMEWORK_VENDOR_DIR}/shdoc/shdoc" <"${file}" || { - Log::displayError "parse error of file ${file}" - return 0 - } - ) || true -} - -# @description log message to file -# @arg $1 message:String the message to display -Log::logSkipped() { - if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_INFO)); then - Log::logMessage "${2:-SKIPPED}" "$1" - fi -} - # @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 @@ -1722,7 +1564,7 @@ optionHelpCallback() { # shellcheck disable=SC2317 # if function is overridden optionVersionCallback() { - echo "${SCRIPT_NAME} version 1.0" + echo "${SCRIPT_NAME} version 1.1" exit 0 } @@ -1919,30 +1761,6 @@ commandOptionParseFinished() { displayConfig fi } - -# shellcheck disable=SC2317 # if function is overridden -updateOptionVendorCallback() { - BASH_FRAMEWORK_ARGV_FILTERED+=("$1" "$2") -} - -# shellcheck disable=SC2317 # if function is overridden -updateOptionBashVersionCallback() { - BASH_FRAMEWORK_ARGV_FILTERED+=("$1" "$2") -} - -# shellcheck disable=SC2317 # if function is overridden -updateOptionBashBaseImageCallback() { - BASH_FRAMEWORK_ARGV_FILTERED+=("$1" "$2") -} - -# shellcheck disable=SC2317 # if function is overridden -updateOptionBranchNameCallback() { - BASH_FRAMEWORK_ARGV_FILTERED+=("$1" "$2") -} - -declare optionVendor="ubuntu" -declare optionBashVersion="5.1" -declare optionBashBaseImage="ubuntu:20.04" declare optionContinuousIntegrationMode=0 # shellcheck disable=SC2317 # if function is overridden @@ -1950,35 +1768,14 @@ updateOptionContinuousIntegrationMode() { BASH_FRAMEWORK_ARGV_FILTERED+=("$1") } -declare optionSkipDockerBuild=0 - -# shellcheck disable=SC2317 # if function is overridden -updateOptionSkipDockerBuildCallback() { - BASH_FRAMEWORK_ARGV_FILTERED+=("$1") -} - docCommand() { local options_parse_cmd="$1" shift || true if [[ "${options_parse_cmd}" = "parse" ]]; then - optionVendor="ubuntu" - local -i options_parse_optionParsedCountOptionVendor - ((options_parse_optionParsedCountOptionVendor = 0)) || true - optionBashVersion="5.1" - local -i options_parse_optionParsedCountOptionBashVersion - ((options_parse_optionParsedCountOptionBashVersion = 0)) || true - optionBashBaseImage="ubuntu:20.04" - local -i options_parse_optionParsedCountOptionBashBaseImage - ((options_parse_optionParsedCountOptionBashBaseImage = 0)) || true - local -i options_parse_optionParsedCountOptionBranchName - ((options_parse_optionParsedCountOptionBranchName = 0)) || true optionContinuousIntegrationMode="0" local -i options_parse_optionParsedCountOptionContinuousIntegrationMode ((options_parse_optionParsedCountOptionContinuousIntegrationMode = 0)) || true - optionSkipDockerBuild="0" - local -i options_parse_optionParsedCountOptionSkipDockerBuild - ((options_parse_optionParsedCountOptionSkipDockerBuild = 0)) || true local -i options_parse_optionParsedCountOptionBashFrameworkConfig ((options_parse_optionParsedCountOptionBashFrameworkConfig = 0)) || true optionConfig="0" @@ -2020,79 +1817,7 @@ docCommand() { local options_parse_arg="$1" local argOptDefaultBehavior=0 case "${options_parse_arg}" in - # Option 1/20 - # Option optionVendor --vendor variableType String min 0 max 1 authorizedValues 'alpine|ubuntu' regexp '' - --vendor) - shift - if (($# == 0)); then - Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - a value needs to be specified" - return 1 - fi - if [[ ! "$1" =~ alpine|ubuntu ]]; then - Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - value '$1' is not part of authorized values(alpine|ubuntu)" - return 1 - fi - if ((options_parse_optionParsedCountOptionVendor >= 1)); then - Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" - return 1 - fi - ((++options_parse_optionParsedCountOptionVendor)) - # shellcheck disable=SC2034 - optionVendor="$1" - updateOptionVendorCallback "${options_parse_arg}" "${optionVendor}" - ;; - # Option 2/20 - # Option optionBashVersion --bash-version variableType String min 0 max 1 authorizedValues '' regexp '' - --bash-version) - 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_optionParsedCountOptionBashVersion >= 1)); then - Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" - return 1 - fi - ((++options_parse_optionParsedCountOptionBashVersion)) - # shellcheck disable=SC2034 - optionBashVersion="$1" - updateOptionBashVersionCallback "${options_parse_arg}" "${optionBashVersion}" - ;; - # Option 3/20 - # Option optionBashBaseImage --bash-base-image variableType String min 0 max 1 authorizedValues '' regexp '' - --bash-base-image) - 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_optionParsedCountOptionBashBaseImage >= 1)); then - Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" - return 1 - fi - ((++options_parse_optionParsedCountOptionBashBaseImage)) - # shellcheck disable=SC2034 - optionBashBaseImage="$1" - updateOptionBashBaseImageCallback "${options_parse_arg}" "${optionBashBaseImage}" - ;; - # Option 4/20 - # Option optionBranchName --branch-name variableType String min 0 max 1 authorizedValues '' regexp '' - --branch-name) - 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_optionParsedCountOptionBranchName >= 1)); then - Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" - return 1 - fi - ((++options_parse_optionParsedCountOptionBranchName)) - # shellcheck disable=SC2034 - optionBranchName="$1" - updateOptionBranchNameCallback "${options_parse_arg}" "${optionBranchName}" - ;; - # Option 5/20 + # Option 1/15 # Option optionContinuousIntegrationMode --ci variableType Boolean min 0 max 1 authorizedValues '' regexp '' --ci) # shellcheck disable=SC2034 @@ -2104,19 +1829,7 @@ docCommand() { ((++options_parse_optionParsedCountOptionContinuousIntegrationMode)) updateOptionContinuousIntegrationMode "${options_parse_arg}" ;; - # Option 6/20 - # Option optionSkipDockerBuild --skip-docker-build variableType Boolean min 0 max 1 authorizedValues '' regexp '' - --skip-docker-build) - # shellcheck disable=SC2034 - optionSkipDockerBuild="1" - if ((options_parse_optionParsedCountOptionSkipDockerBuild >= 1)); then - Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" - return 1 - fi - ((++options_parse_optionParsedCountOptionSkipDockerBuild)) - updateOptionSkipDockerBuildCallback "${options_parse_arg}" - ;; - # Option 7/20 + # Option 2/15 # Option optionBashFrameworkConfig --bash-framework-config variableType String min 0 max 1 authorizedValues '' regexp '' --bash-framework-config) shift @@ -2133,7 +1846,7 @@ docCommand() { optionBashFrameworkConfig="$1" optionBashFrameworkConfigCallback "${options_parse_arg}" "${optionBashFrameworkConfig}" ;; - # Option 8/20 + # Option 3/15 # Option optionConfig --config variableType Boolean min 0 max 1 authorizedValues '' regexp '' --config) # shellcheck disable=SC2034 @@ -2144,7 +1857,7 @@ docCommand() { fi ((++options_parse_optionParsedCountOptionConfig)) ;; - # Option 9/20 + # Option 4/15 # Option optionInfoVerbose --verbose|-v variableType Boolean min 0 max 1 authorizedValues '' regexp '' --verbose | -v) # shellcheck disable=SC2034 @@ -2157,7 +1870,7 @@ docCommand() { optionInfoVerboseCallback "${options_parse_arg}" updateArgListInfoVerboseCallback "${options_parse_arg}" ;; - # Option 10/20 + # Option 5/15 # Option optionDebugVerbose -vv variableType Boolean min 0 max 1 authorizedValues '' regexp '' -vv) # shellcheck disable=SC2034 @@ -2170,7 +1883,7 @@ docCommand() { optionDebugVerboseCallback "${options_parse_arg}" updateArgListDebugVerboseCallback "${options_parse_arg}" ;; - # Option 11/20 + # Option 6/15 # Option optionTraceVerbose -vvv variableType Boolean min 0 max 1 authorizedValues '' regexp '' -vvv) # shellcheck disable=SC2034 @@ -2183,7 +1896,7 @@ docCommand() { optionTraceVerboseCallback "${options_parse_arg}" updateArgListTraceVerboseCallback "${options_parse_arg}" ;; - # Option 12/20 + # Option 7/15 # Option optionEnvFiles --env-file variableType StringArray min 0 max -1 authorizedValues '' regexp '' --env-file) shift @@ -2196,7 +1909,7 @@ docCommand() { optionEnvFileCallback "${options_parse_arg}" "${optionEnvFiles[@]}" updateArgListEnvFileCallback "${options_parse_arg}" "${optionEnvFiles[@]}" ;; - # Option 13/20 + # Option 8/15 # Option optionNoColor --no-color variableType Boolean min 0 max 1 authorizedValues '' regexp '' --no-color) # shellcheck disable=SC2034 @@ -2209,7 +1922,7 @@ docCommand() { optionNoColorCallback "${options_parse_arg}" updateArgListNoColorCallback "${options_parse_arg}" ;; - # Option 14/20 + # Option 9/15 # Option optionTheme --theme variableType String min 0 max 1 authorizedValues 'default|default-force|noColor' regexp '' --theme) shift @@ -2231,7 +1944,7 @@ docCommand() { optionThemeCallback "${options_parse_arg}" "${optionTheme}" updateArgListThemeCallback "${options_parse_arg}" "${optionTheme}" ;; - # Option 15/20 + # Option 10/15 # Option optionHelp --help|-h variableType Boolean min 0 max 1 authorizedValues '' regexp '' --help | -h) # shellcheck disable=SC2034 @@ -2243,7 +1956,7 @@ docCommand() { ((++options_parse_optionParsedCountOptionHelp)) optionHelpCallback "${options_parse_arg}" ;; - # Option 16/20 + # Option 11/15 # Option optionVersion --version variableType Boolean min 0 max 1 authorizedValues '' regexp '' --version) # shellcheck disable=SC2034 @@ -2255,7 +1968,7 @@ docCommand() { ((++options_parse_optionParsedCountOptionVersion)) optionVersionCallback "${options_parse_arg}" ;; - # Option 17/20 + # Option 12/15 # Option optionQuiet --quiet|-q variableType Boolean min 0 max 1 authorizedValues '' regexp '' --quiet | -q) # shellcheck disable=SC2034 @@ -2268,7 +1981,7 @@ docCommand() { optionQuietCallback "${options_parse_arg}" updateArgListQuietCallback "${options_parse_arg}" ;; - # Option 18/20 + # Option 13/15 # Option optionLogLevel --log-level variableType String min 0 max 1 authorizedValues 'OFF|ERR|ERROR|WARN|WARNING|INFO|DEBUG|TRACE' regexp '' --log-level) shift @@ -2290,7 +2003,7 @@ docCommand() { optionLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" updateArgListLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" ;; - # Option 19/20 + # Option 14/15 # Option optionLogFile --log-file variableType String min 0 max 1 authorizedValues '' regexp '' --log-file) shift @@ -2308,7 +2021,7 @@ docCommand() { optionLogFileCallback "${options_parse_arg}" "${optionLogFile}" updateArgListLogFileCallback "${options_parse_arg}" "${optionLogFile}" ;; - # Option 20/20 + # Option 15/15 # Option optionDisplayLevel --display-level variableType String min 0 max 1 authorizedValues 'OFF|ERR|ERROR|WARN|WARNING|INFO|DEBUG|TRACE' regexp '' --display-level) shift @@ -2355,43 +2068,14 @@ docCommand() { echo -e "$(Array::wrap2 " " 80 2 "${__HELP_TITLE_COLOR}USAGE:${__RESET_COLOR}" "${SCRIPT_NAME}" "[OPTIONS]")" echo -e "$(Array::wrap2 " " 80 2 "${__HELP_TITLE_COLOR}USAGE:${__RESET_COLOR}" \ "${SCRIPT_NAME}" \ - "[--vendor ]" "[--bash-version ]" "[--bash-base-image ]" "[--branch-name ]" "[--ci]" "[--skip-docker-build]" "[--bash-framework-config ]" "[--config]" "[--verbose|-v]" "[-vv]" "[-vvv]" "[--env-file ]" "[--no-color]" "[--theme ]" "[--help|-h]" "[--version]" "[--quiet|-q]" "[--log-level ]" "[--log-file ]" "[--display-level ]")" + "[--ci]" "[--bash-framework-config ]" "[--config]" "[--verbose|-v]" "[-vv]" "[-vvv]" "[--env-file ]" "[--no-color]" "[--theme ]" "[--help|-h]" "[--version]" "[--quiet|-q]" "[--log-level ]" "[--log-file ]" "[--display-level ]")" echo echo -e "${__HELP_TITLE_COLOR}OPTIONS:${__RESET_COLOR}" - echo -e " ${__HELP_OPTION_COLOR}--vendor ${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(vendor\ image\ to\ use:\ alpine\|ubuntu) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo ' Default value: ubuntu' - echo ' Possible values: alpine|ubuntu' - echo -e " ${__HELP_OPTION_COLOR}--bash-version ${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(version\ of\ bash\ to\ use:\ 4.4\|5.0\|5.1\|5.2) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo ' Default value: 5.1' - echo -e " ${__HELP_OPTION_COLOR}--bash-base-image ${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(bash\ bash\ image\ to\ use\ \(eg:\ ubuntu:20.04\,\ amd64/bash:4.4-alpine3.18\)) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo ' Default value: ubuntu:20.04' - echo -e " ${__HELP_OPTION_COLOR}--branch-name ${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(branch\ name\ being\ built\,\ will\ help\ to\ create\ docker\ image\ tag\ name) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" echo -e " ${__HELP_OPTION_COLOR}--ci${__HELP_NORMAL} {single}" local -a helpArray # shellcheck disable=SC2054 helpArray=(activate\ continuous\ integration\ mode\ \(tmp\ folder\ not\ shared\ with\ host\)) echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo -e " ${__HELP_OPTION_COLOR}--skip-docker-build${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(skip\ docker\ image\ build\ if\ option\ provided) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" echo echo -e "${__HELP_TITLE_COLOR}GLOBAL OPTIONS:${__RESET_COLOR}" echo -e " ${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} {single}" @@ -2472,7 +2156,7 @@ docCommand() { INTERNAL TOOL""" echo echo -n -e "${__HELP_TITLE_COLOR}VERSION: ${__RESET_COLOR}" - echo '1.0' + echo '1.1' echo echo -e "${__HELP_TITLE_COLOR}AUTHOR:${__RESET_COLOR}" echo '[François Chastanet](https://github.com/fchastanet)' @@ -2491,41 +2175,50 @@ INTERNAL TOOL""" } declare copyrightBeginYear="2022" -readonly defaultVendor="ubuntu" -readonly defaultBashVersion="5.1" -readonly defaultBashBaseImage="ubuntu:20.04" - docCommand parse "${BASH_FRAMEWORK_ARGV[@]}" -run() { - PAGES_DIR="${FRAMEWORK_ROOT_DIR}/pages" - - if [[ "${IN_BASH_DOCKER:-}" != "You're in docker" ]]; then - ShellDoc::installRequirementsIfNeeded - Softwares::installHadolint - Softwares::installShellcheck +runContainer() { + local image="scrasnups/build:bash-tools-ubuntu-5.3" + local -a dockerRunCmd=( + "/bash/bin/doc" + "${BASH_FRAMEWORK_ARGV_FILTERED[@]}" + ) - # shellcheck disable=SC2034 - 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" ) - # shellcheck disable=SC2034 - local -a dockerArgvFiltered=() - # shellcheck disable=SC2154 - Docker::runBuildContainer \ - "${optionVendor:-ubuntu}" \ - "${optionBashVersion:-5.1}" \ - "${optionBashBaseImage:-ubuntu:20.04}" \ - "${optionSkipDockerBuild}" \ - "${optionTraceVerbose}" \ - "${optionContinuousIntegrationMode}" \ - dockerRunCmd \ - dockerArgvFiltered - - exit $? 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 #----------------------------- @@ -2596,6 +2289,21 @@ run() { fi } +installRequirements() { + ShellDoc::installRequirementsIfNeeded + Softwares::installHadolint + Softwares::installShellcheck +} + +run() { + if [[ "${IN_BASH_DOCKER:-}" != "You're in docker" ]]; then + installRequirements + runContainer + else + generateDoc + fi +} + if [[ "${BASH_FRAMEWORK_QUIET_MODE:-0}" = "1" ]]; then run &>/dev/null else diff --git a/bin/runBuildContainer b/bin/runBuildContainer deleted file mode 100755 index c8daf2d5..00000000 --- a/bin/runBuildContainer +++ /dev/null @@ -1,1729 +0,0 @@ -#!/usr/bin/env bash -############################################################################### -# GENERATED FACADE FROM https://github.com/fchastanet/bash-tools-framework/tree/master/src/_binaries/runBuildContainer.sh -# DO NOT EDIT IT -# @generated -############################################################################### -# shellcheck disable=SC2288,SC2034 -# BIN_FILE=${FRAMEWORK_ROOT_DIR}/bin/runBuildContainer -# VAR_RELATIVE_FRAMEWORK_DIR_TO_CURRENT_DIR=.. -# FACADE - -# ensure that no user aliases could interfere with -# commands used in this script -unalias -a || true -shopt -u expand_aliases - -# shellcheck disable=SC2034 -((failures = 0)) || true - -# Bash will remember & return the highest exit code in a chain of pipes. -# This way you can catch the error inside pipes, e.g. mysqldump | gzip -set -o pipefail -set -o errexit - -# Command Substitution can inherit errexit option since bash v4.4 -shopt -s inherit_errexit || true - -# if set, and job control is not active, the shell runs the last command -# of a pipeline not executed in the background in the current shell -# environment. -shopt -s lastpipe - -# a log is generated when a command fails -set -o errtrace - -# use nullglob so that (file*.php) will return an empty array if no file -# matches the wildcard -shopt -s nullglob - -# ensure regexp are interpreted without accentuated characters -export LC_ALL=POSIX - -export TERM=xterm-256color - -# avoid interactive install -export DEBIAN_FRONTEND=noninteractive -export DEBCONF_NONINTERACTIVE_SEEN=true - -# store command arguments for later usage -# shellcheck disable=SC2034 -declare -a BASH_FRAMEWORK_ARGV=("$@") -# shellcheck disable=SC2034 -declare -a ORIGINAL_BASH_FRAMEWORK_ARGV=("$@") - -# @see https://unix.stackexchange.com/a/386856 -# shellcheck disable=SC2317 -interruptManagement() { - # restore SIGINT handler - trap - INT - # ensure that Ctrl-C is trapped by this script and not by sub process - # report to the parent that we have indeed been interrupted - 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 -################################################ - -KEEP_TEMP_FILES="${KEEP_TEMP_FILES:-0}" -export KEEP_TEMP_FILES - -# PERSISTENT_TMPDIR is not deleted by traps -PERSISTENT_TMPDIR="${TMPDIR:-/tmp}/bash-framework" -export PERSISTENT_TMPDIR -if [[ ! -d "${PERSISTENT_TMPDIR}" ]]; then - mkdir -p "${PERSISTENT_TMPDIR}" -fi - -# shellcheck disable=SC2034 -TMPDIR="$(mktemp -d -p "${PERSISTENT_TMPDIR:-/tmp}" -t bash-framework-$$-XXXXXX)" -export TMPDIR - -# temp dir cleaning -# shellcheck disable=SC2317 -cleanOnExit() { - local rc=$? - if [[ "${KEEP_TEMP_FILES:-0}" = "1" ]]; then - Log::displayInfo "KEEP_TEMP_FILES=1 temp files kept here '${TMPDIR}'" - elif [[ -n "${TMPDIR+xxx}" ]]; then - Log::displayDebug "KEEP_TEMP_FILES=0 removing temp files '${TMPDIR}'" - rm -Rf "${TMPDIR:-/tmp/fake}" >/dev/null 2>&1 - fi - exit "${rc}" -} -trap cleanOnExit EXIT HUP QUIT ABRT TERM - -# @description Log namespace provides 2 kind of functions -# - Log::display* allows to display given message with -# given display level -# - Log::log* allows to log given message with -# given log level -# Log::display* functions automatically log the message too -# @see Env::requireLoad to load the display and log level from .env file - -# @description log level off -export __LEVEL_OFF=0 -# @description log level error -export __LEVEL_ERROR=1 -# @description log level warning -export __LEVEL_WARNING=2 -# @description log level info -export __LEVEL_INFO=3 -# @description log level success -export __LEVEL_SUCCESS=3 -# @description log level debug -export __LEVEL_DEBUG=4 - -# @description verbose level off -export __VERBOSE_LEVEL_OFF=0 -# @description verbose level info -export __VERBOSE_LEVEL_INFO=1 -# @description verbose level info -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 - fi - Log::logInfo "$1" "${type}" -} - -# @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 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) -# @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 - fi - Log::logError "$1" -} - -# @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) -# @arg $@ args:String[] -# @set __ERROR_COLOR String indicate error status -# @set __INFO_COLOR String indicate info status -# @set __SUCCESS_COLOR String indicate success status -# @set __WARNING_COLOR String indicate warning status -# @set __SKIPPED_COLOR String indicate skipped status -# @set __DEBUG_COLOR String indicate debug status -# @set __HELP_COLOR String indicate help status -# @set __TEST_COLOR String not used -# @set __TEST_ERROR_COLOR String not used -# @set __HELP_TITLE_COLOR String used to display help title in help strings -# @set __HELP_OPTION_COLOR String used to display highlight options in help strings -# -# @set __RESET_COLOR String reset default color -# -# @set __HELP_EXAMPLE String to remove -# @set __HELP_TITLE String to remove -# @set __HELP_NORMAL String to remove -# shellcheck disable=SC2034 -UI::theme() { - local theme="${1-default}" - if [[ ! "${theme}" =~ -force$ ]] && ! Assert::tty; then - theme="noColor" - fi - 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 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 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 temp file using default TMPDIR variable -# initialized in _includes/_commonHeader.sh -# @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 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 -# environment variables loaded by Env::requireLoad -# try to create log file and rotate it if necessary -# @noargs -# @set BASH_FRAMEWORK_LOG_LEVEL int to OFF level if BASH_FRAMEWORK_LOG_FILE is empty or not writable -# @env BASH_FRAMEWORK_DISPLAY_LEVEL int -# @env BASH_FRAMEWORK_LOG_LEVEL int -# @env BASH_FRAMEWORK_LOG_FILE String -# @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() { - if [[ -z "${BASH_FRAMEWORK_LOG_FILE:-}" ]]; then - BASH_FRAMEWORK_LOG_LEVEL=${__LEVEL_OFF} - export BASH_FRAMEWORK_LOG_LEVEL - fi - - if ((BASH_FRAMEWORK_LOG_LEVEL > __LEVEL_OFF)); then - if [[ ! -f "${BASH_FRAMEWORK_LOG_FILE}" ]]; then - if [[ ! -d "${BASH_FRAMEWORK_LOG_FILE%/*}" ]]; then - if ! mkdir -p "${BASH_FRAMEWORK_LOG_FILE%/*}" 2>/dev/null; then - BASH_FRAMEWORK_LOG_LEVEL=${__LEVEL_OFF} - echo -e "${__ERROR_COLOR}ERROR - directory ${BASH_FRAMEWORK_LOG_FILE%/*} is not writable${__RESET_COLOR}" >&2 - fi - elif ! touch --no-create "${BASH_FRAMEWORK_LOG_FILE}" 2>/dev/null; then - BASH_FRAMEWORK_LOG_LEVEL=${__LEVEL_OFF} - echo -e "${__ERROR_COLOR}ERROR - File ${BASH_FRAMEWORK_LOG_FILE} is not writable${__RESET_COLOR}" >&2 - fi - elif [[ ! -w "${BASH_FRAMEWORK_LOG_FILE}" ]]; then - BASH_FRAMEWORK_LOG_LEVEL=${__LEVEL_OFF} - echo -e "${__ERROR_COLOR}ERROR - File ${BASH_FRAMEWORK_LOG_FILE} is not writable${__RESET_COLOR}" >&2 - fi - fi - - if ((BASH_FRAMEWORK_LOG_LEVEL > __LEVEL_OFF)); then - # will always be created even if not in info level - Log::logMessage "INFO" "Logging to file ${BASH_FRAMEWORK_LOG_FILE} - Log level ${BASH_FRAMEWORK_LOG_LEVEL}" - if ((BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION > 0)); then - Log::rotate "${BASH_FRAMEWORK_LOG_FILE}" "${BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION}" - fi - fi -} - -# @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 checkout usage doc below -# -# [DockerNamespace usage](DockerUsage.md ':include') - -# @description run the container specified by args provided. -# build and push the image if needed -# -# @env DOCKER_BUILD_OPTIONS -# @env SKIP_USER -# @env USER_ID -# @env GROUP_ID -# @env FRAMEWORK_ROOT_DIR -# @env DOCKER_OPTION_IMAGE_TAG -# @env BASH_FRAMEWORK_ARGV_FILTERED -Docker::runBuildContainer() { - local optionVendor="$1" - local optionBashVersion="$2" - local optionBashBaseImage="$3" - local optionSkipDockerBuild="$4" - local optionTraceVerbose="$5" - local optionContinuousIntegrationMode="$6" - local -n localDockerRunCmd=$7 - local -n localDockerRunArgs=$8 - - if [[ -d "$(pwd)/vendor/bash-tools-framework" ]]; then - localDockerRunArgs+=( - -v "$(cd "$(pwd)/vendor/bash-tools-framework" && pwd -P):/bash/vendor/bash-tools-framework" - ) - fi - - # shellcheck disable=SC2154 - if [[ "${optionContinuousIntegrationMode}" = "0" ]]; then - localDockerRunArgs+=(-v "/tmp:/tmp") - fi - localDockerRunArgs+=(-e KEEP_TEMP_FILES="${KEEP_TEMP_FILES}") - localDockerRunArgs+=(-e BATS_FIX_TEST="${BATS_FIX_TEST:-0}") - - # shellcheck disable=SC2154 - Log::displayInfo "Using ${optionVendor}:${optionBashVersion}" - - local imageRef="${DOCKER_OPTION_IMAGE_TAG:-build:bash-tools-${optionVendor}-${optionBashVersion}}" - if [[ "${optionSkipDockerBuild:-0}" != "1" ]]; then - Log::displayInfo "Build docker image ${imageRef}" - # shellcheck disable=SC2154 - ( - if [[ "${optionTraceVerbose}" = "1" ]]; then - set -x - fi - Docker::buildPushDockerImage \ - "${optionVendor}" \ - "${optionBashVersion}" \ - "${optionBashBaseImage}" \ - "${optionPush}" \ - "${optionTraceVerbose}" - ) - fi - if [[ -f "${FRAMEWORK_ROOT_DIR}/.docker/DockerfileUser" ]]; then - local imageRefUser="${imageRef}-user" - if [[ "${optionSkipDockerBuild:-0}" != "1" ]]; then - Log::displayInfo "build docker image ${imageRefUser} with user configuration" - # shellcheck disable=SC2154 - ( - if [[ "${optionTraceVerbose}" = "1" ]]; then - set -x - fi - # shellcheck disable=SC2086 - DOCKER_BUILDKIT=1 docker build \ - ${DOCKER_BUILD_OPTIONS} \ - --cache-from "scrasnups/${imageRef}" \ - --build-arg "BASH_IMAGE=scrasnups/${imageRef}" \ - --build-arg SKIP_USER="${SKIP_USER:-0}" \ - --build-arg USER_ID="${USER_ID:-$(id -u)}" \ - --build-arg GROUP_ID="${GROUP_ID:-$(id -g)}" \ - -f "${FRAMEWORK_ROOT_DIR}/.docker/DockerfileUser" \ - -t "${imageRefUser}" \ - "${FRAMEWORK_ROOT_DIR}/.docker" - ) - fi - fi - - Log::displayDebug "Run container with localDockerRunCmd: ${localDockerRunCmd[*]}" - Log::displayDebug "Run container with localDockerRunArgs: ${localDockerRunArgs[*]}" - Log::displayDebug "Run container with BASH_FRAMEWORK_ARGV_FILTERED: ${BASH_FRAMEWORK_ARGV_FILTERED[*]}" - ( - # shellcheck disable=SC2154 - if [[ "${optionTraceVerbose}" = "1" ]]; then - set -x - fi - # shellcheck disable=SC2086 - docker run \ - --rm \ - "${localDockerRunArgs[@]}" \ - ${DOCKER_RUN_OPTIONS} \ - -w /bash \ - -v "$(pwd):/bash" \ - --user "${USER_ID:-$(id -u)}:${GROUP_ID:-$(id -g)}" \ - "${imageRefUser}" \ - "${localDockerRunCmd[@]}" - ) -} - -# @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="" - -# @description compute duration since last call to this function -# the result is set in following env variables. -# in ss.sss (seconds followed by milliseconds precision 3 decimals) -# @noargs -# @env DISPLAY_DURATION int (default 0) if 1 display elapsed time information between 2 info logs -# @set LOG_LAST_LOG_DATE_INIT int (default 1) set to 0 at first call, allows to detect reference log -# @set LOG_LAST_DURATION_STR String the last duration displayed -# @set LOG_LAST_LOG_DATE String the last log date that will be used to compute next diff -Log::computeDuration() { - if ((${DISPLAY_DURATION:-0} == 1)); then - local -i duration=0 - local -i delta=0 - local -i currentLogDate - currentLogDate="${EPOCHREALTIME/[^0-9]/}" - if ((LOG_LAST_LOG_DATE_INIT == 1)); then - LOG_LAST_LOG_DATE_INIT=0 - LOG_LAST_DURATION_STR="Ref" - else - duration=$(((currentLogDate - FIRST_LOG_DATE) / 1000000)) - delta=$(((currentLogDate - LOG_LAST_LOG_DATE) / 1000000)) - LOG_LAST_DURATION_STR="${duration}s/+${delta}s" - fi - LOG_LAST_LOG_DATE="${currentLogDate}" - # shellcheck disable=SC2034 - local microSeconds="${EPOCHREALTIME#*.}" - LOG_LAST_DURATION_STR="$(printf '%(%T)T.%03.0f\n' "${EPOCHSECONDS}" "${microSeconds:0:3}")(${LOG_LAST_DURATION_STR}) - " - else - # shellcheck disable=SC2034 - LOG_LAST_DURATION_STR="" - fi -} - -# @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 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 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 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 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 -} - -# @description log message to file -# @arg $1 message:String the message to display -Log::logFatal() { - Log::logMessage "${2:-FATAL}" "$1" -} - -# @description Internal: common log message -# @example text -# [date]|[levelMsg]|message -# -# @example text -# 2020-01-19 19:20:21|ERROR |log error -# 2020-01-19 19:20:21|SKIPPED|log skipped -# -# @arg $1 levelMsg:String message's level description (eg: STATUS, ERROR, ...) -# @arg $2 msg:String the message to display -# @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() { - local levelMsg="$1" - local msg="$2" - local date - - if [[ -n "${BASH_FRAMEWORK_LOG_FILE}" ]] && ((BASH_FRAMEWORK_LOG_LEVEL > __LEVEL_OFF)); then - date="$(date '+%Y-%m-%d %H:%M:%S')" - touch "${BASH_FRAMEWORK_LOG_FILE}" - printf "%s|%7s|%s\n" "${date}" "${levelMsg}" "${msg}" >>"${BASH_FRAMEWORK_LOG_FILE}" - 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 -Log::rotate() { - local file="$1" - local maxLogFilesCount="${2:-5}" - - if [[ ! -f "${file}" ]]; then - Log::displayDebug "Log file ${file} doesn't exist yet" - return 0 - fi - local i - for ((i = maxLogFilesCount - 1; i > 0; i--)); do - Log::displayInfo "Log rotation ${file}.${i} to ${file}.$((i + 1))" - mv "${file}."{"${i}","$((i + 1))"} &>/dev/null || true - done - if cp "${file}" "${file}.1" &>/dev/null; then - echo >"${file}" # reset log file - Log::displayInfo "Log rotation ${file} to ${file}.1" - fi -} - -# @description build image and push it to registry -# @env DOCKER_OPTION_IMAGE_TAG String computed from optionVendor and optionBashVersion if not provided -# @env DOCKER_OPTION_IMAGE String default scrasnups/${DOCKER_OPTION_IMAGE_TAG} -# @env DOCKER_BUILD_OPTIONS String list of docker arguments to pass to docker build command -# @env FRAMEWORK_ROOT_DIR String path allowing to deduce .docker/Dockerfile.{vendor} -Docker::buildPushDockerImage() { - local optionVendor="$1" - local optionBashVersion="$2" - local optionBashBaseImage="$3" - local optionPush="$4" - local optionTraceVerbose="$5" - # parameters based on env variables - local imageTag="${DOCKER_OPTION_IMAGE_TAG:-build:bash-tools-${optionVendor}-${optionBashVersion}}" - local image="${DOCKER_OPTION_IMAGE:-scrasnups/${imageTag}}" - local DOCKER_BUILD_OPTIONS="${DOCKER_BUILD_OPTIONS:-}" - - Log::displayInfo "Pull image ${image}" - ( - if [[ "${optionTraceVerbose}" = "1" ]]; then - set -x - fi - docker pull "${image}" || true - ) - - Log::displayInfo "Build image ${image}" - # shellcheck disable=SC2086 - ( - if [[ "${optionTraceVerbose}" = "1" ]]; then - set -x - fi - DOCKER_BUILDKIT=1 docker build \ - ${DOCKER_BUILD_OPTIONS} \ - -f "${FRAMEWORK_ROOT_DIR}/.docker/Dockerfile.${optionVendor}" \ - --cache-from "${image}" \ - --build-arg BUILDKIT_INLINE_CACHE=1 \ - --build-arg argBashVersion="${optionBashVersion}" \ - --build-arg BASH_IMAGE="${optionBashBaseImage}" \ - -t "${imageTag}" \ - -t "${image}" \ - "${FRAMEWORK_ROOT_DIR}/.docker" - ) - - Log::displayInfo "Image ${image} - bash version check" - docker run --rm "${imageTag}" bash --version - - # shellcheck disable=SC2154 - if [[ "${optionPush}" = "1" ]]; then - Log::displayInfo "Push image ${image}" - ( - if [[ "${optionTraceVerbose}" = "1" ]]; then - set -x - fi - docker push "scrasnups/${imageTag}" - ) - 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 -Env::pathPrepend() { - local arg - for arg in "$@"; do - if [[ -d "${arg}" && ":${PATH}:" != *":${arg}:"* ]]; then - PATH="$(realpath "${arg}"):${PATH}" - fi - done -} - -# @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() { - if [[ "${LOAD_THEME:-1}" = "1" ]]; then - UI::theme "${BASH_FRAMEWORK_THEME-default}" - fi -} - -# FUNCTIONS - -facade_main_runBuildContainersh() { -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 -UI::requireTheme -Log::requireLoad -Compiler::Facade::requireCommandBinDir - -# @require Compiler::Facade::requireCommandBinDir - -declare -a BASH_FRAMEWORK_ARGV_FILTERED=() - -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() { - runBuildContainerCommand help - exit 0 -} - -# shellcheck disable=SC2317 # if function is overridden -optionVersionCallback() { - echo "${SCRIPT_NAME} version 1.0" - 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() { - 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}" - ;; - *) - Log::displayError "Command ${SCRIPT_NAME} - Invalid level ${level}" - return 1 - esac -} - -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}")" - 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 -} - -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$|\.tpl$|/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 - # 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 -} - -# shellcheck disable=SC2317 # if function is overridden -updateOptionVendorCallback() { - BASH_FRAMEWORK_ARGV_FILTERED+=("$1" "$2") -} - -# shellcheck disable=SC2317 # if function is overridden -updateOptionBashVersionCallback() { - BASH_FRAMEWORK_ARGV_FILTERED+=("$1" "$2") -} - -# shellcheck disable=SC2317 # if function is overridden -updateOptionBashBaseImageCallback() { - BASH_FRAMEWORK_ARGV_FILTERED+=("$1" "$2") -} - -# shellcheck disable=SC2317 # if function is overridden -updateOptionBranchNameCallback() { - BASH_FRAMEWORK_ARGV_FILTERED+=("$1" "$2") -} - -declare optionVendor="ubuntu" -declare optionBashVersion="5.1" -declare optionBashBaseImage="ubuntu:20.04" -declare optionContinuousIntegrationMode=0 - -# shellcheck disable=SC2317 # if function is overridden -updateOptionContinuousIntegrationMode() { - BASH_FRAMEWORK_ARGV_FILTERED+=("$1") -} - -declare optionSkipDockerBuild=0 - -# shellcheck disable=SC2317 # if function is overridden -updateOptionSkipDockerBuildCallback() { - BASH_FRAMEWORK_ARGV_FILTERED+=("$1") -} - -runBuildContainerCommand() { - local options_parse_cmd="$1" - shift || true - - if [[ "${options_parse_cmd}" = "parse" ]]; then - optionVendor="ubuntu" - local -i options_parse_optionParsedCountOptionVendor - ((options_parse_optionParsedCountOptionVendor = 0)) || true - optionBashVersion="5.1" - local -i options_parse_optionParsedCountOptionBashVersion - ((options_parse_optionParsedCountOptionBashVersion = 0)) || true - optionBashBaseImage="ubuntu:20.04" - local -i options_parse_optionParsedCountOptionBashBaseImage - ((options_parse_optionParsedCountOptionBashBaseImage = 0)) || true - local -i options_parse_optionParsedCountOptionBranchName - ((options_parse_optionParsedCountOptionBranchName = 0)) || true - optionContinuousIntegrationMode="0" - local -i options_parse_optionParsedCountOptionContinuousIntegrationMode - ((options_parse_optionParsedCountOptionContinuousIntegrationMode = 0)) || true - optionSkipDockerBuild="0" - local -i options_parse_optionParsedCountOptionSkipDockerBuild - ((options_parse_optionParsedCountOptionSkipDockerBuild = 0)) || true - local -i options_parse_optionParsedCountOptionBashFrameworkConfig - ((options_parse_optionParsedCountOptionBashFrameworkConfig = 0)) || true - optionConfig="0" - local -i options_parse_optionParsedCountOptionConfig - ((options_parse_optionParsedCountOptionConfig = 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 - optionNoColor="0" - local -i options_parse_optionParsedCountOptionNoColor - ((options_parse_optionParsedCountOptionNoColor = 0)) || true - optionTheme="default" - local -i options_parse_optionParsedCountOptionTheme - ((options_parse_optionParsedCountOptionTheme = 0)) || true - optionHelp="0" - local -i options_parse_optionParsedCountOptionHelp - ((options_parse_optionParsedCountOptionHelp = 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 - local -i options_parse_optionParsedCountOptionLogLevel - ((options_parse_optionParsedCountOptionLogLevel = 0)) || true - local -i options_parse_optionParsedCountOptionLogFile - ((options_parse_optionParsedCountOptionLogFile = 0)) || true - local -i options_parse_optionParsedCountOptionDisplayLevel - ((options_parse_optionParsedCountOptionDisplayLevel = 0)) || true - local -i options_parse_argParsedCountDockerRunCmd - ((options_parse_argParsedCountDockerRunCmd = 0)) || true - # 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/20 - # Option optionVendor --vendor variableType String min 0 max 1 authorizedValues 'alpine|ubuntu' regexp '' - --vendor) - shift - if (($# == 0)); then - Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - a value needs to be specified" - return 1 - fi - if [[ ! "$1" =~ alpine|ubuntu ]]; then - Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - value '$1' is not part of authorized values(alpine|ubuntu)" - return 1 - fi - if ((options_parse_optionParsedCountOptionVendor >= 1)); then - Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" - return 1 - fi - ((++options_parse_optionParsedCountOptionVendor)) - # shellcheck disable=SC2034 - optionVendor="$1" - updateOptionVendorCallback "${options_parse_arg}" "${optionVendor}" - ;; - # Option 2/20 - # Option optionBashVersion --bash-version variableType String min 0 max 1 authorizedValues '' regexp '' - --bash-version) - 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_optionParsedCountOptionBashVersion >= 1)); then - Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" - return 1 - fi - ((++options_parse_optionParsedCountOptionBashVersion)) - # shellcheck disable=SC2034 - optionBashVersion="$1" - updateOptionBashVersionCallback "${options_parse_arg}" "${optionBashVersion}" - ;; - # Option 3/20 - # Option optionBashBaseImage --bash-base-image variableType String min 0 max 1 authorizedValues '' regexp '' - --bash-base-image) - 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_optionParsedCountOptionBashBaseImage >= 1)); then - Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" - return 1 - fi - ((++options_parse_optionParsedCountOptionBashBaseImage)) - # shellcheck disable=SC2034 - optionBashBaseImage="$1" - updateOptionBashBaseImageCallback "${options_parse_arg}" "${optionBashBaseImage}" - ;; - # Option 4/20 - # Option optionBranchName --branch-name variableType String min 0 max 1 authorizedValues '' regexp '' - --branch-name) - 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_optionParsedCountOptionBranchName >= 1)); then - Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" - return 1 - fi - ((++options_parse_optionParsedCountOptionBranchName)) - # shellcheck disable=SC2034 - optionBranchName="$1" - updateOptionBranchNameCallback "${options_parse_arg}" "${optionBranchName}" - ;; - # Option 5/20 - # Option optionContinuousIntegrationMode --ci variableType Boolean min 0 max 1 authorizedValues '' regexp '' - --ci) - # shellcheck disable=SC2034 - optionContinuousIntegrationMode="1" - 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 - ((++options_parse_optionParsedCountOptionContinuousIntegrationMode)) - updateOptionContinuousIntegrationMode "${options_parse_arg}" - ;; - # Option 6/20 - # Option optionSkipDockerBuild --skip-docker-build variableType Boolean min 0 max 1 authorizedValues '' regexp '' - --skip-docker-build) - # shellcheck disable=SC2034 - optionSkipDockerBuild="1" - if ((options_parse_optionParsedCountOptionSkipDockerBuild >= 1)); then - Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" - return 1 - fi - ((++options_parse_optionParsedCountOptionSkipDockerBuild)) - updateOptionSkipDockerBuildCallback "${options_parse_arg}" - ;; - # Option 7/20 - # Option optionBashFrameworkConfig --bash-framework-config variableType String min 0 max 1 authorizedValues '' regexp '' - --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 8/20 - # Option optionConfig --config variableType Boolean min 0 max 1 authorizedValues '' regexp '' - --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 9/20 - # Option optionInfoVerbose --verbose|-v variableType Boolean min 0 max 1 authorizedValues '' regexp '' - --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}" - updateArgListInfoVerboseCallback "${options_parse_arg}" - ;; - # Option 10/20 - # Option optionDebugVerbose -vv variableType Boolean min 0 max 1 authorizedValues '' regexp '' - -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}" - updateArgListDebugVerboseCallback "${options_parse_arg}" - ;; - # Option 11/20 - # Option optionTraceVerbose -vvv variableType Boolean min 0 max 1 authorizedValues '' regexp '' - -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}" - updateArgListTraceVerboseCallback "${options_parse_arg}" - ;; - # Option 12/20 - # Option optionEnvFiles --env-file variableType StringArray min 0 max -1 authorizedValues '' regexp '' - --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 13/20 - # Option optionNoColor --no-color variableType Boolean min 0 max 1 authorizedValues '' regexp '' - --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}" - updateArgListNoColorCallback "${options_parse_arg}" - ;; - # Option 14/20 - # Option optionTheme --theme variableType String min 0 max 1 authorizedValues 'default|default-force|noColor' regexp '' - --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 15/20 - # Option optionHelp --help|-h variableType Boolean min 0 max 1 authorizedValues '' regexp '' - --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}" - ;; - # Option 16/20 - # Option optionVersion --version variableType Boolean min 0 max 1 authorizedValues '' regexp '' - --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}" - ;; - # Option 17/20 - # Option optionQuiet --quiet|-q variableType Boolean min 0 max 1 authorizedValues '' regexp '' - --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}" - updateArgListQuietCallback "${options_parse_arg}" - ;; - # Option 18/20 - # Option optionLogLevel --log-level variableType String min 0 max 1 authorizedValues 'OFF|ERR|ERROR|WARN|WARNING|INFO|DEBUG|TRACE' regexp '' - --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 19/20 - # Option optionLogFile --log-file variableType String min 0 max 1 authorizedValues '' regexp '' - --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 20/20 - # Option optionDisplayLevel --display-level variableType String min 0 max 1 authorizedValues 'OFF|ERR|ERROR|WARN|WARNING|INFO|DEBUG|TRACE' regexp '' - --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}" - ;; - -*) - unknownOption "${options_parse_arg}" - ;; - *) - if ((0)); then - # Technical if - never reached - : - # Argument 1/1 - # Argument dockerRunCmd min 0 max -1 authorizedValues '' regexp '' - elif ((options_parse_parsedArgIndex >= 0)); then - ((++options_parse_argParsedCountDockerRunCmd)) - # shellcheck disable=SC2034 - dockerRunCmd+=("${options_parse_arg}") - else - if [[ "${argOptDefaultBehavior}" = "0" ]]; then - Log::displayError "Command ${SCRIPT_NAME} - Argument - too much arguments provided: $*" - return 1 - fi - fi - ((++options_parse_parsedArgIndex)) - ;; - esac - shift || true - done - commandOptionParseFinished - Log::displayDebug "Command ${SCRIPT_NAME} - parse arguments: ${BASH_FRAMEWORK_ARGV[*]}" - Log::displayDebug "Command ${SCRIPT_NAME} - parse filtered arguments: ${BASH_FRAMEWORK_ARGV_FILTERED[*]}" - elif [[ "${options_parse_cmd}" = "help" ]]; then - Array::wrap2 " " 80 0 "${__HELP_TITLE_COLOR}DESCRIPTION:${__RESET_COLOR}" "run the container eventually building the docker image before." - echo - - echo -e "$(Array::wrap2 " " 80 2 "${__HELP_TITLE_COLOR}USAGE:${__RESET_COLOR}" "${SCRIPT_NAME}" "[OPTIONS]" "[ARGUMENTS]")" - echo -e "$(Array::wrap2 " " 80 2 "${__HELP_TITLE_COLOR}USAGE:${__RESET_COLOR}" \ - "${SCRIPT_NAME}" \ - "[--vendor ]" "[--bash-version ]" "[--bash-base-image ]" "[--branch-name ]" "[--ci]" "[--skip-docker-build]" "[--bash-framework-config ]" "[--config]" "[--verbose|-v]" "[-vv]" "[-vvv]" "[--env-file ]" "[--no-color]" "[--theme ]" "[--help|-h]" "[--version]" "[--quiet|-q]" "[--log-level ]" "[--log-file ]" "[--display-level ]")" - echo - echo -e "${__HELP_TITLE_COLOR}ARGUMENTS:${__RESET_COLOR}" - echo -e " [${__HELP_OPTION_COLOR}arg${__HELP_NORMAL} {list} (optional)]" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(command\ arguments\ including\ command\ name) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo - echo -e "${__HELP_TITLE_COLOR}OPTIONS:${__RESET_COLOR}" - echo -e " ${__HELP_OPTION_COLOR}--vendor ${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(vendor\ image\ to\ use:\ alpine\|ubuntu) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo ' Default value: ubuntu' - echo ' Possible values: alpine|ubuntu' - echo -e " ${__HELP_OPTION_COLOR}--bash-version ${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(version\ of\ bash\ to\ use:\ 4.4\|5.0\|5.1\|5.2) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo ' Default value: 5.1' - echo -e " ${__HELP_OPTION_COLOR}--bash-base-image ${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(bash\ bash\ image\ to\ use\ \(eg:\ ubuntu:20.04\,\ amd64/bash:4.4-alpine3.18\)) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo ' Default value: ubuntu:20.04' - echo -e " ${__HELP_OPTION_COLOR}--branch-name ${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(branch\ name\ being\ built\,\ will\ help\ to\ create\ docker\ image\ tag\ name) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo -e " ${__HELP_OPTION_COLOR}--ci${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(activate\ continuous\ integration\ mode\ \(tmp\ folder\ not\ shared\ with\ host\)) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo -e " ${__HELP_OPTION_COLOR}--skip-docker-build${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(skip\ docker\ image\ build\ if\ option\ provided) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo - echo -e "${__HELP_TITLE_COLOR}GLOBAL OPTIONS:${__RESET_COLOR}" - echo -e " ${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(use\ alternate\ bash\ framework\ configuration.) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo -e " ${__HELP_OPTION_COLOR}--config${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(Display\ configuration) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo -e " ${__HELP_OPTION_COLOR}--verbose${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-v${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(info\ level\ verbose\ mode\ \(alias\ of\ --display-level\ INFO\)) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo -e " ${__HELP_OPTION_COLOR}-vv${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(debug\ level\ verbose\ mode\ \(alias\ of\ --display-level\ DEBUG\)) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo -e " ${__HELP_OPTION_COLOR}-vvv${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(trace\ level\ verbose\ mode\ \(alias\ of\ --display-level\ TRACE\)) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(Load\ the\ specified\ env\ file\ \(deprecated\,\ please\ use\ --bash-framework-config\ option\ instead\)) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(Produce\ monochrome\ output.\ alias\ of\ --theme\ noColor.) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo -e " ${__HELP_OPTION_COLOR}--theme ${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(choose\ color\ theme\ -\ default-force\ means\ colors\ will\ be\ produced\ even\ if\ command\ is\ piped) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo ' Default value: default' - echo ' Possible values: default|default-force|noColor' - echo -e " ${__HELP_OPTION_COLOR}--help${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-h${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(Display\ this\ command\ help) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo -e " ${__HELP_OPTION_COLOR}--version${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(Print\ version\ information\ and\ quit) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo -e " ${__HELP_OPTION_COLOR}--quiet${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-q${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(quiet\ mode\,\ doesn\'t\ display\ any\ output) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo -e " ${__HELP_OPTION_COLOR}--log-level ${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(Set\ log\ level) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo ' Possible values: OFF|ERR|ERROR|WARN|WARNING|INFO|DEBUG|TRACE' - echo -e " ${__HELP_OPTION_COLOR}--log-file ${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(Set\ log\ file) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo -e " ${__HELP_OPTION_COLOR}--display-level ${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(set\ display\ level) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo ' Possible values: OFF|ERR|ERROR|WARN|WARNING|INFO|DEBUG|TRACE' - echo -e """ -run the container specified by args provided. -Command to run is passed via the rest of arguments. -TTY allocation is detected automatically. - -additional env variables can be passed to docker build: - - ${__HELP_OPTION_COLOR}SKIP_USER${__HELP_NORMAL} (default: 0) - - ${__HELP_OPTION_COLOR}USER_ID${__HELP_NORMAL} (default: current user id provided by (id -u) command) - - ${__HELP_OPTION_COLOR}GROUP_ID${__HELP_NORMAL} (default: current group id provided by (id -g) command) - -additional docker run options can be passed - via ${__HELP_OPTION_COLOR}DOCKER_RUN_OPTIONS${__HELP_NORMAL} env variable""" - echo - echo -n -e "${__HELP_TITLE_COLOR}VERSION: ${__RESET_COLOR}" - echo '1.0' - echo - echo -e "${__HELP_TITLE_COLOR}AUTHOR:${__RESET_COLOR}" - echo '[François Chastanet](https://github.com/fchastanet)' - echo - echo -e "${__HELP_TITLE_COLOR}SOURCE FILE:${__RESET_COLOR}" - echo 'https://github.com/fchastanet/bash-tools-framework/tree/master/src/_binaries/runBuildContainer.sh' - echo - echo -e "${__HELP_TITLE_COLOR}LICENSE:${__RESET_COLOR}" - echo 'MIT License' - echo - Array::wrap2 ' ' 76 4 "$(copyrightCallback)" - else - Log::displayError "Command ${SCRIPT_NAME} - Option command invalid: '${options_parse_cmd}'" - return 1 - fi -} -declare copyrightBeginYear="2022" - -# shellcheck disable=SC2317 # if function is overridden -unknownOption() { - dockerRunCmd+=("$1") -} - -# shellcheck disable=SC2034 -declare -a dockerRunCmd=() -# shellcheck disable=SC2034 -declare -a dockerRunArgs=() -export DOCKER_BUILD_OPTIONS="${DOCKER_BUILD_OPTIONS:-}" -export DOCKER_RUN_OPTIONS="${DOCKER_RUN_OPTIONS:-}" -export BASH_FRAMEWORK_ROOT_DIR="${FRAMEWORK_ROOT_DIR}" - -runBuildContainerCommand parse "${BASH_FRAMEWORK_ARGV[@]}" - -run() { - # shellcheck disable=SC2154 - Docker::runBuildContainer \ - "${optionVendor}" \ - "${optionBashVersion}" \ - "${optionBashBaseImage}" \ - "${optionSkipDockerBuild}" \ - "${optionTraceVerbose}" \ - "${optionContinuousIntegrationMode}" \ - dockerRunCmd \ - dockerRunArgs -} - -if [[ "${BASH_FRAMEWORK_QUIET_MODE:-0}" = "1" ]]; then - run &>/dev/null -else - run -fi - -} - -facade_main_runBuildContainersh "$@" diff --git a/bin/shellcheckLint b/bin/shellcheckLint index bd635d4b..26e8412b 100755 --- a/bin/shellcheckLint +++ b/bin/shellcheckLint @@ -157,73 +157,6 @@ Log::displayDebug() { Log::logDebug "$1" } -# @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 install hadolint if necessary -# @arg $1 targetFile:String -# @feature Github::upgradeRelease -Softwares::installShellcheck() { - local targetFile="${1:-${FRAMEWORK_VENDOR_BIN_DIR}/shellcheck}" - # shellcheck disable=SC2317 - install() { - local file="$1" - local targetFile="$2" - local version="$3" - local tempDir - tempDir="$(mktemp -d -p "${TMPDIR:-/tmp}" -t bash-framework-shellcheck-$$-XXXXXX)" - ( - cd "${tempDir}" || exit 1 - tar -xJvf "${file}" >&2 - mv "shellcheck-v${version}/shellcheck" "${targetFile}" - chmod +x "${targetFile}" - rm -f "${file}" || true - ) - } - INSTALL_CALLBACK=install Github::upgradeRelease \ - "${targetFile}" \ - "https://github.com/koalaman/shellcheck/releases/download/v@latestVersion@/shellcheck-v@latestVersion@.linux.x86_64.tar.xz" -} - # @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 @@ -676,146 +609,6 @@ Log::logDebug() { fi } -# @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}' \ - "$@" -} - -# @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 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}" -} - -# @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 upgrade given binary to latest github release using retry -# -# downloadReleaseUrl argument : the placeholder @latestVersion@ will be replaced by the latest release version -# @arg $1 targetFile:String target binary file (eg: /usr/local/bin/kind) -# @arg $2 downloadReleaseUrl:String github release url (eg: https://github.com/kubernetes-sigs/kind/releases/download/@latestVersion@/kind-linux-amd64) -# @arg $3 softVersionArg:String parameter to add to existing command to compute current version -# @arg $4 softVersionCallback:Function function called to get software version (default: Version::getCommandVersionFromPlainText will call software with argument --version) -# @arg $5 installCallback:Function function called to install the file retrieved on github (default copy as is and set execution bit) -# @arg $6 softVersionCallback:Function function to call to filter the version retrieved from github (Default: Version::parse) -# @stdout log messages about retry, install, upgrade -# @env SOFT_VERSION_CALLBACK pass softVersionCallback by env variable instead of passing it by arg -# @env INSTALL_CALLBACK pass installCallback by env variable instead of passing it by arg -# @env CURL_CONNECT_TIMEOUT number of seconds before giving up host connection -# @env EXACT_VERSION if provided, retrieve exact version instead of the latest -Github::upgradeRelease() { - local targetFile="$1" - local downloadReleaseUrl="$2" - local softVersionArg="${3:---version}" - local softVersionCallback="${4:-${SOFT_VERSION_CALLBACK:-Version::getCommandVersionFromPlainText}}" - # shellcheck disable=SC2034 - local installCallback="${5:-${INSTALL_CALLBACK:-}}" - local parseGithubVersionCallback="${6:-${PARSE_VERSION_CALLBACK:-Version::parse}}" - - local repo - repo="$(Github::extractRepoFromGithubUrl "${downloadReleaseUrl}")" - local releasesUrl="https://api.github.com/repos/${repo}/releases/latest" - - # shellcheck disable=SC2317 - extractVersion() { - Version::githubApiExtractVersion | "${parseGithubVersionCallback}" - } - FILTER_LAST_VERSION_CALLBACK=${FILTER_LAST_VERSION_CALLBACK:-extractVersion} \ - SOFT_VERSION_CALLBACK="${softVersionCallback}" \ - Web::upgradeRelease \ - "${targetFile}" \ - "${releasesUrl}" \ - "${downloadReleaseUrl}" \ - "${softVersionArg}" \ - "${EXACT_VERSION:-}" -} - # @description log message to file # @arg $1 message:String the message to display Log::logWarning() { @@ -925,251 +718,6 @@ UI::requireTheme() { 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 -Version::getCommandVersionFromPlainText() { - local command="$1" - local argVersion="${2:---version}" - "${command}" "${argVersion}" 2>&1 | - Version::parse # keep only version numbers -} - -# @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 -# @stdout the repo in the form owner/repo -Github::extractRepoFromGithubUrl() { - local githubUrl="$1" - local result - result="$(sed -n -E 's#^https://github.com/([^/]+/[^/]+)/.*$#\1#p' <<<"${githubUrl}")" - if [[ -z "${result}" ]]; then - return 1 - fi - echo "${result}" -} - -# @description extract version number from github api -# @noargs -# @stdin json result of github API -# @exitcode 1 if jq or Version::parse fails -# @stdout the version parsed -# @require Linux::requireJqCommand -Version::githubApiExtractVersion() { - jq -r ".tag_name" -} - -# @description upgrade given binary to latest release using retry -# -# releasesUrl argument : the placeholder @latestVersion@ will be replaced by the latest release version -# @arg $1 targetFile:String target binary file (eg: /usr/local/bin/kind) -# @arg $2 releasesUrl:String url on which we can query all available versions (eg: "https://go.dev/dl/?mode=json") -# @arg $3 downloadReleaseUrl:String url from which the software will be downloaded (eg: https://storage.googleapis.com/golang/go@latestVersion@.linux-amd64.tar.gz) -# @arg $4 softVersionArg:String parameter to add to existing command to compute current version -# @arg $5 exactVersion:String if you want to retrieve a specific version instead of the latest -# @stdout log messages about retry, install, upgrade -# @env FILTER_LAST_VERSION_CALLBACK a callback to filter the latest version from releasesUrl -# @env SOFT_VERSION_CALLBACK a callback to execute command version -# @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 -Web::upgradeRelease() { - local targetFile="$1" - local releasesUrl="$2" - local downloadReleaseUrl="$3" - local softVersionArg="${4:---version}" - local exactVersion="${5:-}" - # options from env variables - 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 - exactVersion="${latestVersion}" - fi - local url="${downloadReleaseUrl//@latestVersion@/${exactVersion}}" - if [[ -n "${exactVersion}" ]] && ! Github::isReleaseVersionExist "${url}"; then - Log::displayError "${targetFile} version ${exactVersion} doesn't exist on github" - return 2 - fi - if [[ "${currentVersion}" = "${exactVersion}" ]]; then - Log::displayInfo "${targetFile} version ${exactVersion} already installed" - else - if [[ -z "${currentVersion}" ]]; then - Log::displayInfo "Installing ${targetFile} with version ${exactVersion}" - else - Log::displayInfo "Upgrading ${targetFile} from version ${currentVersion} to ${exactVersion}" - fi - Log::displayInfo "Using url ${url}" - newSoftware=$(mktemp -p "${TMPDIR:-/tmp}" -t web.newSoftware.XXXX) - Retry::default curl \ - -L \ - --connect-timeout "${CURL_CONNECT_TIMEOUT:-5}" \ - -o "${newSoftware}" \ - --fail \ - "${url}" - - Github::defaultInstall "${newSoftware}" "${targetFile}" "${exactVersion}" "${installCallback}" - fi -} - -# @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 -# @env CURL_CONNECT_TIMEOUT number of seconds before giving up host connection -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}" -} - -# @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 -# @exitcode 0 if release version exists -# @env CURL_CONNECT_TIMEOUT number of seconds before giving up host connection -Github::isReleaseVersionExist() { - local releaseUrl="$1" - - curl \ - -L \ - --connect-timeout "${CURL_CONNECT_TIMEOUT:-5}" \ - -o /dev/null \ - --silent \ - --head \ - --fail \ - "${releaseUrl}" -} - -# @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 -# @exitcode 1 if max retries count reached -# @env RETRY_MAX_RETRY int max retries -# @env RETRY_DELAY_BETWEEN_RETRIES int delay between attempts -Retry::default() { - Retry::parameterized "${RETRY_MAX_RETRY:-5}" "${RETRY_DELAY_BETWEEN_RETRIES:-15}" "" "$@" -} - -# @description intermediate callback that is used by Github::upgradeRelease -# or Github::installRelease -# if installCallback is not set, it allows to: -# - copy the downloaded file to the right target file -# - and set the execution bit -# else -# installCallback is called with newSoftware, targetFile, version arguments -# fi -# @warning do not use this function as callback for Github::upgradeRelease or Github::installRelease, as it would result to an infinite loop -# @arg $1 newSoftware:String the downloaded software file -# @arg $2 targetFile:String where we want to copy the file -# @arg $3 version:String the version that has been downloaded -# @arg $4 installCallback:Function (optional) the callback to call with 3 first arguments -# @env SUDO String allows to use custom sudo prefix command -# @exitcode * on failure -# @see Github::upgradeRelease -# @see Github::installRelease -# @internal -Github::defaultInstall() { - local newSoftware="$1" - local targetFile="$2" - local version="$3" - local installCallback=$4 - # shellcheck disable=SC2086 - if ! ${SUDO:-} test -d "${targetFile%/*}"; then - ${SUDO:-} mkdir -p "${targetFile%/*}" - fi - if [[ "$(type -t "${installCallback}")" = "function" ]]; then - ${installCallback} "${newSoftware}" "${targetFile}" "${version}" - else - ${SUDO:-} mv "${newSoftware}" "${targetFile}" - ${SUDO:-} chmod +x "${targetFile}" - hash -r - ${SUDO:-} rm -f "${newSoftware}" || true - Log::displaySuccess "Version ${version} installed in ${targetFile}" - fi -} - -# @description ensure command jq is available -# @exitcode 1 if jq command not available -# @stderr diagnostics information is displayed -Linux::requireJqCommand() { - if [[ "${SKIP_REQUIRE_JQ:-0}" = "0" && "${SKIP_REQUIRES:-0}" = "0" ]]; then - Assert::commandExists jq - fi -} - -# @description Retry a command several times depending on parameters -# @arg $1 maxRetries:int $1 max retries -# @arg $2 delay:int between attempt -# @arg $3 message:String to display to describe the attempt -# @arg $@ rest of parameters, the command to run -# @exitcode 0 on success -# @exitcode 1 if max retries count reached -# @exitcode 2 if maxRetries invalid value -Retry::parameterized() { - local maxRetries=$1 - shift || true - local delayBetweenTries=$1 - shift || true - local message="$1" - shift || true - local retriesCount=1 - if [[ "${maxRetries}" -lt 1 ]]; then - Log::displayError "invalid maxRetry value" - return 2 - fi - - while true; do - Log::displayInfo "Attempt ${retriesCount}/${maxRetries}: ${message}" - if "$@"; then - break - elif [[ "${retriesCount}" -lt "${maxRetries}" ]]; then - Log::displayDebug "Command failed. Wait for ${delayBetweenTries} seconds" - ((retriesCount++)) - sleep "${delayBetweenTries}" - else - Log::displayError "The command has failed after ${retriesCount} attempts." - return 1 - fi - done - return 0 -} - -# @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 -# @env LOG_CONTEXT String allows to contextualize the log -Log::displaySuccess() { - if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_INFO)); then - Log::computeDuration - echo -e "${__SUCCESS_COLOR}SUCCESS - ${LOG_CONTEXT:-}${LOG_LAST_DURATION_STR:-}${1}${__RESET_COLOR}" >&2 - fi - Log::logSuccess "$1" -} - -# @description log message to file -# @arg $1 message:String the message to display -Log::logSuccess() { - if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_INFO)); then - Log::logMessage "${2:-SUCCESS}" "$1" - fi -} - # FUNCTIONS facade_main_shellcheckLintsh() { @@ -1182,16 +730,10 @@ FRAMEWORK_VENDOR_BIN_DIR="${FRAMEWORK_ROOT_DIR}/vendor/bin" Env::requireLoad UI::requireTheme Log::requireLoad -Linux::requireJqCommand Compiler::Facade::requireCommandBinDir # @require Compiler::Facade::requireCommandBinDir -# check if command in PATH is already the minimal version needed -if ! Version::checkMinimal "${FRAMEWORK_VENDOR_BIN_DIR}/shellcheck" "--version" "${MIN_SHELLCHECK_VERSION}" >/dev/null 2>&1; then - Softwares::installShellcheck -fi - declare -a BASH_FRAMEWORK_ARGV_FILTERED=() copyrightCallback() { @@ -1937,13 +1479,9 @@ argShellcheckFilesCallback() { } shellcheckLintParseCallback() { if [[ "${optionStaged}" = "1" ]] && ((${#argShellcheckFiles[@]} > 0)); then - Log::displayWarning "${SCRIPT_NAME} - --staged option ignored as files as been provided" + Log::displayWarning "${SCRIPT_NAME} - --staged option ignored as files have been provided" optionStaged="0" fi - if [[ "${optionFormat}" != "tty" && "${optionXargs}" = "1" ]]; then - Log::displayWarning "--xargs option ignored as only supported with tty format" - optionXargs="0" - fi shellcheckArgs=(-f "${optionFormat}") } @@ -2038,13 +1576,31 @@ run() { if [[ "${optionTraceVerbose}" = "1" ]]; then xargsArgs+=(-t) fi - + local tmpDir + tmpDir="$(mktemp -d -p "${TMPDIR:-/tmp}" -t bash-tools-shellcheck-XXXXXX)" + export tmpDir + # shellcheck disable=SC2016 echo "${files[@]}" | optionTraceVerbose="${optionTraceVerbose}" \ shellcheckArgsStr="${shellcheckArgs[*]}" \ xargs "${xargsArgs[@]}" \ - bash -c 'shellcheckFunction $@' bash - + bash -c 'echo >&2 "linting $* ..."; file="$(md5sum <<<"$@")"; shellcheckFunction $@ | tee "${tmpDir}/${file%% *}" >&2; echo >&2 "linted $*"' bash + if [[ "${optionFormat}" = "checkstyle" ]]; then + ( + echo "" + echo "" + awk '//{flag=0} flag' "${tmpDir}/"* + echo "" + ) + elif [[ "${optionFormat}" = "json" ]]; then + ( + jq '.[]' "${tmpDir}/"* | jq -s '.' + ) + elif [[ "${optionFormat}" = "json1" ]]; then + jq -s 'map(.comments[])' "${tmpDir}/"* | jq '{"comments": .}' + else + cat "${tmpDir}/"* + fi ) else Log::displayWarning "no file provided" diff --git a/bin/test b/bin/test deleted file mode 100755 index 020dc430..00000000 --- a/bin/test +++ /dev/null @@ -1,2028 +0,0 @@ -#!/usr/bin/env bash -############################################################################### -# GENERATED FACADE FROM https://github.com/fchastanet/bash-tools-framework/tree/master/src/_binaries/test.sh -# DO NOT EDIT IT -# @generated -############################################################################### -# shellcheck disable=SC2288,SC2034 -# BIN_FILE=${FRAMEWORK_ROOT_DIR}/bin/test -# FACADE - -# ensure that no user aliases could interfere with -# commands used in this script -unalias -a || true -shopt -u expand_aliases - -# shellcheck disable=SC2034 -((failures = 0)) || true - -# Bash will remember & return the highest exit code in a chain of pipes. -# This way you can catch the error inside pipes, e.g. mysqldump | gzip -set -o pipefail -set -o errexit - -# Command Substitution can inherit errexit option since bash v4.4 -shopt -s inherit_errexit || true - -# if set, and job control is not active, the shell runs the last command -# of a pipeline not executed in the background in the current shell -# environment. -shopt -s lastpipe - -# a log is generated when a command fails -set -o errtrace - -# use nullglob so that (file*.php) will return an empty array if no file -# matches the wildcard -shopt -s nullglob - -# ensure regexp are interpreted without accentuated characters -export LC_ALL=POSIX - -export TERM=xterm-256color - -# avoid interactive install -export DEBIAN_FRONTEND=noninteractive -export DEBCONF_NONINTERACTIVE_SEEN=true - -# store command arguments for later usage -# shellcheck disable=SC2034 -declare -a BASH_FRAMEWORK_ARGV=("$@") -# shellcheck disable=SC2034 -declare -a ORIGINAL_BASH_FRAMEWORK_ARGV=("$@") - -# @see https://unix.stackexchange.com/a/386856 -# shellcheck disable=SC2317 -interruptManagement() { - # restore SIGINT handler - trap - INT - # ensure that Ctrl-C is trapped by this script and not by sub process - # report to the parent that we have indeed been interrupted - 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 -################################################ - -KEEP_TEMP_FILES="${KEEP_TEMP_FILES:-0}" -export KEEP_TEMP_FILES - -# PERSISTENT_TMPDIR is not deleted by traps -PERSISTENT_TMPDIR="${TMPDIR:-/tmp}/bash-framework" -export PERSISTENT_TMPDIR -if [[ ! -d "${PERSISTENT_TMPDIR}" ]]; then - mkdir -p "${PERSISTENT_TMPDIR}" -fi - -# shellcheck disable=SC2034 -TMPDIR="$(mktemp -d -p "${PERSISTENT_TMPDIR:-/tmp}" -t bash-framework-$$-XXXXXX)" -export TMPDIR - -# temp dir cleaning -# shellcheck disable=SC2317 -cleanOnExit() { - local rc=$? - if [[ "${KEEP_TEMP_FILES:-0}" = "1" ]]; then - Log::displayInfo "KEEP_TEMP_FILES=1 temp files kept here '${TMPDIR}'" - elif [[ -n "${TMPDIR+xxx}" ]]; then - Log::displayDebug "KEEP_TEMP_FILES=0 removing temp files '${TMPDIR}'" - rm -Rf "${TMPDIR:-/tmp/fake}" >/dev/null 2>&1 - fi - exit "${rc}" -} -trap cleanOnExit EXIT HUP QUIT ABRT TERM - -# @description Log namespace provides 2 kind of functions -# - Log::display* allows to display given message with -# given display level -# - Log::log* allows to log given message with -# given log level -# Log::display* functions automatically log the message too -# @see Env::requireLoad to load the display and log level from .env file - -# @description log level off -export __LEVEL_OFF=0 -# @description log level error -export __LEVEL_ERROR=1 -# @description log level warning -export __LEVEL_WARNING=2 -# @description log level info -export __LEVEL_INFO=3 -# @description log level success -export __LEVEL_SUCCESS=3 -# @description log level debug -export __LEVEL_DEBUG=4 - -# @description verbose level off -export __VERBOSE_LEVEL_OFF=0 -# @description verbose level info -export __VERBOSE_LEVEL_INFO=1 -# @description verbose level info -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 - fi - Log::logInfo "$1" "${type}" -} - -# @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" -} - -BASH_FRAMEWORK_BATS_DEPENDENCIES_CHECK_TIMEOUT=86400 # 1 day - -# @description install requirements to execute bats -# @warning the following repositories are shallow cloned -# @warning cloning is skipped if vendor/.batsInstalled file exists -# @warning a new check is done everyday -# @warning repository is not updated if a change is detected -# @env BASH_FRAMEWORK_BATS_DEPENDENCIES_CHECK_TIMEOUT int default value 86400 (86400 seconds = 1 day) -# @set BASH_FRAMEWORK_BATS_DEPENDENCIES_INSTALLED String the file created when all git shallow clones have succeeded -# @see https://github.com/bats-core/bats-core -# @see https://github.com/bats-core/bats-support -# @see https://github.com/bats-core/bats-assert -# @see https://github.com/Flamefire/bats-mock -# @stderr diagnostics information is displayed -# @feature Git::shallowClone -Bats::installRequirementsIfNeeded() { - local rootDir="${1:-"${FRAMEWORK_ROOT_DIR}"}" - BASH_FRAMEWORK_BATS_DEPENDENCIES_INSTALLED="${rootDir}/vendor/.batsInstalled" - if [[ "$( - Cache::getFileContentIfNotExpired \ - "${BASH_FRAMEWORK_BATS_DEPENDENCIES_INSTALLED}" \ - "${BASH_FRAMEWORK_BATS_DEPENDENCIES_CHECK_TIMEOUT}" - )" != "1" ]] \ - ; then - Log::displayInfo "Install or update bats requirements" - Git::shallowClone \ - "https://github.com/bats-core/bats-core.git" \ - "${rootDir}/vendor/bats" \ - "master" \ - "FORCE_DELETION" - - Git::shallowClone \ - "https://github.com/bats-core/bats-support.git" \ - "${rootDir}/vendor/bats-support" \ - "master" \ - "FORCE_DELETION" - - Git::shallowClone \ - "https://github.com/bats-core/bats-assert.git" \ - "${rootDir}/vendor/bats-assert" \ - "master" \ - "FORCE_DELETION" - - Git::shallowClone \ - "https://github.com/Flamefire/bats-mock.git" \ - "${rootDir}/vendor/bats-mock-Flamefire" \ - "master" \ - "FORCE_DELETION" - - echo "1" >"${BASH_FRAMEWORK_BATS_DEPENDENCIES_INSTALLED}" - 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 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 -# @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 load colors theme constants -# @warning if tty not opened, noColor theme will be chosen -# @arg $1 theme:String the theme to use (default, noColor) -# @arg $@ args:String[] -# @set __ERROR_COLOR String indicate error status -# @set __INFO_COLOR String indicate info status -# @set __SUCCESS_COLOR String indicate success status -# @set __WARNING_COLOR String indicate warning status -# @set __SKIPPED_COLOR String indicate skipped status -# @set __DEBUG_COLOR String indicate debug status -# @set __HELP_COLOR String indicate help status -# @set __TEST_COLOR String not used -# @set __TEST_ERROR_COLOR String not used -# @set __HELP_TITLE_COLOR String used to display help title in help strings -# @set __HELP_OPTION_COLOR String used to display highlight options in help strings -# -# @set __RESET_COLOR String reset default color -# -# @set __HELP_EXAMPLE String to remove -# @set __HELP_TITLE String to remove -# @set __HELP_NORMAL String to remove -# shellcheck disable=SC2034 -UI::theme() { - local theme="${1-default}" - if [[ ! "${theme}" =~ -force$ ]] && ! Assert::tty; then - theme="noColor" - fi - 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 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 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 temp file using default TMPDIR variable -# initialized in _includes/_commonHeader.sh -# @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 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 -# environment variables loaded by Env::requireLoad -# try to create log file and rotate it if necessary -# @noargs -# @set BASH_FRAMEWORK_LOG_LEVEL int to OFF level if BASH_FRAMEWORK_LOG_FILE is empty or not writable -# @env BASH_FRAMEWORK_DISPLAY_LEVEL int -# @env BASH_FRAMEWORK_LOG_LEVEL int -# @env BASH_FRAMEWORK_LOG_FILE String -# @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() { - if [[ -z "${BASH_FRAMEWORK_LOG_FILE:-}" ]]; then - BASH_FRAMEWORK_LOG_LEVEL=${__LEVEL_OFF} - export BASH_FRAMEWORK_LOG_LEVEL - fi - - if ((BASH_FRAMEWORK_LOG_LEVEL > __LEVEL_OFF)); then - if [[ ! -f "${BASH_FRAMEWORK_LOG_FILE}" ]]; then - if [[ ! -d "${BASH_FRAMEWORK_LOG_FILE%/*}" ]]; then - if ! mkdir -p "${BASH_FRAMEWORK_LOG_FILE%/*}" 2>/dev/null; then - BASH_FRAMEWORK_LOG_LEVEL=${__LEVEL_OFF} - echo -e "${__ERROR_COLOR}ERROR - directory ${BASH_FRAMEWORK_LOG_FILE%/*} is not writable${__RESET_COLOR}" >&2 - fi - elif ! touch --no-create "${BASH_FRAMEWORK_LOG_FILE}" 2>/dev/null; then - BASH_FRAMEWORK_LOG_LEVEL=${__LEVEL_OFF} - echo -e "${__ERROR_COLOR}ERROR - File ${BASH_FRAMEWORK_LOG_FILE} is not writable${__RESET_COLOR}" >&2 - fi - elif [[ ! -w "${BASH_FRAMEWORK_LOG_FILE}" ]]; then - BASH_FRAMEWORK_LOG_LEVEL=${__LEVEL_OFF} - echo -e "${__ERROR_COLOR}ERROR - File ${BASH_FRAMEWORK_LOG_FILE} is not writable${__RESET_COLOR}" >&2 - fi - fi - - if ((BASH_FRAMEWORK_LOG_LEVEL > __LEVEL_OFF)); then - # will always be created even if not in info level - Log::logMessage "INFO" "Logging to file ${BASH_FRAMEWORK_LOG_FILE} - Log level ${BASH_FRAMEWORK_LOG_LEVEL}" - if ((BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION > 0)); then - Log::rotate "${BASH_FRAMEWORK_LOG_FILE}" "${BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION}" - fi - fi -} - -# @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 checkout usage doc below -# -# [DockerNamespace usage](DockerUsage.md ':include') - -# @description run the container specified by args provided. -# build and push the image if needed -# -# @env DOCKER_BUILD_OPTIONS -# @env SKIP_USER -# @env USER_ID -# @env GROUP_ID -# @env FRAMEWORK_ROOT_DIR -# @env DOCKER_OPTION_IMAGE_TAG -# @env BASH_FRAMEWORK_ARGV_FILTERED -Docker::runBuildContainer() { - local optionVendor="$1" - local optionBashVersion="$2" - local optionBashBaseImage="$3" - local optionSkipDockerBuild="$4" - local optionTraceVerbose="$5" - local optionContinuousIntegrationMode="$6" - local -n localDockerRunCmd=$7 - local -n localDockerRunArgs=$8 - - if [[ -d "$(pwd)/vendor/bash-tools-framework" ]]; then - localDockerRunArgs+=( - -v "$(cd "$(pwd)/vendor/bash-tools-framework" && pwd -P):/bash/vendor/bash-tools-framework" - ) - fi - - # shellcheck disable=SC2154 - if [[ "${optionContinuousIntegrationMode}" = "0" ]]; then - localDockerRunArgs+=(-v "/tmp:/tmp") - fi - localDockerRunArgs+=(-e KEEP_TEMP_FILES="${KEEP_TEMP_FILES}") - localDockerRunArgs+=(-e BATS_FIX_TEST="${BATS_FIX_TEST:-0}") - - # shellcheck disable=SC2154 - Log::displayInfo "Using ${optionVendor}:${optionBashVersion}" - - local imageRef="${DOCKER_OPTION_IMAGE_TAG:-build:bash-tools-${optionVendor}-${optionBashVersion}}" - if [[ "${optionSkipDockerBuild:-0}" != "1" ]]; then - Log::displayInfo "Build docker image ${imageRef}" - # shellcheck disable=SC2154 - ( - if [[ "${optionTraceVerbose}" = "1" ]]; then - set -x - fi - Docker::buildPushDockerImage \ - "${optionVendor}" \ - "${optionBashVersion}" \ - "${optionBashBaseImage}" \ - "${optionPush}" \ - "${optionTraceVerbose}" - ) - fi - if [[ -f "${FRAMEWORK_ROOT_DIR}/.docker/DockerfileUser" ]]; then - local imageRefUser="${imageRef}-user" - if [[ "${optionSkipDockerBuild:-0}" != "1" ]]; then - Log::displayInfo "build docker image ${imageRefUser} with user configuration" - # shellcheck disable=SC2154 - ( - if [[ "${optionTraceVerbose}" = "1" ]]; then - set -x - fi - # shellcheck disable=SC2086 - DOCKER_BUILDKIT=1 docker build \ - ${DOCKER_BUILD_OPTIONS} \ - --cache-from "scrasnups/${imageRef}" \ - --build-arg "BASH_IMAGE=scrasnups/${imageRef}" \ - --build-arg SKIP_USER="${SKIP_USER:-0}" \ - --build-arg USER_ID="${USER_ID:-$(id -u)}" \ - --build-arg GROUP_ID="${GROUP_ID:-$(id -g)}" \ - -f "${FRAMEWORK_ROOT_DIR}/.docker/DockerfileUser" \ - -t "${imageRefUser}" \ - "${FRAMEWORK_ROOT_DIR}/.docker" - ) - fi - fi - - Log::displayDebug "Run container with localDockerRunCmd: ${localDockerRunCmd[*]}" - Log::displayDebug "Run container with localDockerRunArgs: ${localDockerRunArgs[*]}" - Log::displayDebug "Run container with BASH_FRAMEWORK_ARGV_FILTERED: ${BASH_FRAMEWORK_ARGV_FILTERED[*]}" - ( - # shellcheck disable=SC2154 - if [[ "${optionTraceVerbose}" = "1" ]]; then - set -x - fi - # shellcheck disable=SC2086 - docker run \ - --rm \ - "${localDockerRunArgs[@]}" \ - ${DOCKER_RUN_OPTIONS} \ - -w /bash \ - -v "$(pwd):/bash" \ - --user "${USER_ID:-$(id -u)}:${GROUP_ID:-$(id -g)}" \ - "${imageRefUser}" \ - "${localDockerRunCmd[@]}" - ) -} - -# @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="" - -# @description compute duration since last call to this function -# the result is set in following env variables. -# in ss.sss (seconds followed by milliseconds precision 3 decimals) -# @noargs -# @env DISPLAY_DURATION int (default 0) if 1 display elapsed time information between 2 info logs -# @set LOG_LAST_LOG_DATE_INIT int (default 1) set to 0 at first call, allows to detect reference log -# @set LOG_LAST_DURATION_STR String the last duration displayed -# @set LOG_LAST_LOG_DATE String the last log date that will be used to compute next diff -Log::computeDuration() { - if ((${DISPLAY_DURATION:-0} == 1)); then - local -i duration=0 - local -i delta=0 - local -i currentLogDate - currentLogDate="${EPOCHREALTIME/[^0-9]/}" - if ((LOG_LAST_LOG_DATE_INIT == 1)); then - LOG_LAST_LOG_DATE_INIT=0 - LOG_LAST_DURATION_STR="Ref" - else - duration=$(((currentLogDate - FIRST_LOG_DATE) / 1000000)) - delta=$(((currentLogDate - LOG_LAST_LOG_DATE) / 1000000)) - LOG_LAST_DURATION_STR="${duration}s/+${delta}s" - fi - LOG_LAST_LOG_DATE="${currentLogDate}" - # shellcheck disable=SC2034 - local microSeconds="${EPOCHREALTIME#*.}" - LOG_LAST_DURATION_STR="$(printf '%(%T)T.%03.0f\n' "${EPOCHSECONDS}" "${microSeconds:0:3}")(${LOG_LAST_DURATION_STR}) - " - else - # shellcheck disable=SC2034 - LOG_LAST_DURATION_STR="" - fi -} - -# @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 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 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 -# @stdout {String} the file content if not expired -# @exitcode 1 if file does not exists -# @exitcode 2 if file expired -Cache::getFileContentIfNotExpired() { - local file="$1" - local maxDuration="$2" - - if [[ ! -f "${file}" ]]; then - return 1 - fi - if (($(File::elapsedTimeSinceLastModification "${file}") > maxDuration)); then - return 2 - fi - cat "${file}" -} - -# @description shallow clone a repository at specific commit sha, tag or branch -# or update repo if already exists -# -# @warning **USE THIS FORCE_DELETION ARGUMENT WITH CAUTION !!!** as the directory will be deleted without any prompt -# -# @arg $1 repository:String -# @arg $2 installDir:String Install dir -# @arg $3 revision:String commit sha, tag or branch -# @arg $4 forceDeletion:Boolean (optional) put "FORCE_DELETION" to force directory deletion if directory exists and it's not a git repository (default: 0) -# -# @exitcode !=0 if git failure or directory not writable -# @exitcode 1 if destination dir already exists and force option is not 1 -# @stderr display verbose status of the command execution -Git::shallowClone() { - local repository="$1" - local installDir="$2" - local revision="$3" - local forceDeletion="${4:-0}" - - if [[ -d "${installDir}/.git" ]]; then - Log::displayInfo "Repository ${installDir} already installed" - else - if [[ -f "${installDir}" || -d "${installDir}" ]]; then - if [[ "${forceDeletion}" = "FORCE_DELETION" ]]; then - Log::displayWarning "Removing ${installDir} ..." - rm -Rf "${installDir}" || exit 1 - else - Log::displayError "Destination ${installDir} already exists, use force option to automatically delete the destination" - return 1 - fi - fi - ( - Log::displayInfo "Installing ${installDir} ..." - if [[ ! -d "${installDir}" ]]; then - mkdir -p "${installDir}" - fi - cd "${installDir}" || exit 1 - git init >&2 - git remote add origin "${repository}" >&2 - ) - fi - ( - cd "${installDir}" || exit 1 - git -c advice.detachedHead=false fetch --progress --depth 1 origin "${revision}" >&2 - git reset --hard FETCH_HEAD >&2 - ) -} - -# @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 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 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 -} - -# @description log message to file -# @arg $1 message:String the message to display -Log::logFatal() { - Log::logMessage "${2:-FATAL}" "$1" -} - -# @description Internal: common log message -# @example text -# [date]|[levelMsg]|message -# -# @example text -# 2020-01-19 19:20:21|ERROR |log error -# 2020-01-19 19:20:21|SKIPPED|log skipped -# -# @arg $1 levelMsg:String message's level description (eg: STATUS, ERROR, ...) -# @arg $2 msg:String the message to display -# @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() { - local levelMsg="$1" - local msg="$2" - local date - - if [[ -n "${BASH_FRAMEWORK_LOG_FILE}" ]] && ((BASH_FRAMEWORK_LOG_LEVEL > __LEVEL_OFF)); then - date="$(date '+%Y-%m-%d %H:%M:%S')" - touch "${BASH_FRAMEWORK_LOG_FILE}" - printf "%s|%7s|%s\n" "${date}" "${levelMsg}" "${msg}" >>"${BASH_FRAMEWORK_LOG_FILE}" - 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 -Log::rotate() { - local file="$1" - local maxLogFilesCount="${2:-5}" - - if [[ ! -f "${file}" ]]; then - Log::displayDebug "Log file ${file} doesn't exist yet" - return 0 - fi - local i - for ((i = maxLogFilesCount - 1; i > 0; i--)); do - Log::displayInfo "Log rotation ${file}.${i} to ${file}.$((i + 1))" - mv "${file}."{"${i}","$((i + 1))"} &>/dev/null || true - done - if cp "${file}" "${file}.1" &>/dev/null; then - echo >"${file}" # reset log file - Log::displayInfo "Log rotation ${file} to ${file}.1" - fi -} - -# @description build image and push it to registry -# @env DOCKER_OPTION_IMAGE_TAG String computed from optionVendor and optionBashVersion if not provided -# @env DOCKER_OPTION_IMAGE String default scrasnups/${DOCKER_OPTION_IMAGE_TAG} -# @env DOCKER_BUILD_OPTIONS String list of docker arguments to pass to docker build command -# @env FRAMEWORK_ROOT_DIR String path allowing to deduce .docker/Dockerfile.{vendor} -Docker::buildPushDockerImage() { - local optionVendor="$1" - local optionBashVersion="$2" - local optionBashBaseImage="$3" - local optionPush="$4" - local optionTraceVerbose="$5" - # parameters based on env variables - local imageTag="${DOCKER_OPTION_IMAGE_TAG:-build:bash-tools-${optionVendor}-${optionBashVersion}}" - local image="${DOCKER_OPTION_IMAGE:-scrasnups/${imageTag}}" - local DOCKER_BUILD_OPTIONS="${DOCKER_BUILD_OPTIONS:-}" - - Log::displayInfo "Pull image ${image}" - ( - if [[ "${optionTraceVerbose}" = "1" ]]; then - set -x - fi - docker pull "${image}" || true - ) - - Log::displayInfo "Build image ${image}" - # shellcheck disable=SC2086 - ( - if [[ "${optionTraceVerbose}" = "1" ]]; then - set -x - fi - DOCKER_BUILDKIT=1 docker build \ - ${DOCKER_BUILD_OPTIONS} \ - -f "${FRAMEWORK_ROOT_DIR}/.docker/Dockerfile.${optionVendor}" \ - --cache-from "${image}" \ - --build-arg BUILDKIT_INLINE_CACHE=1 \ - --build-arg argBashVersion="${optionBashVersion}" \ - --build-arg BASH_IMAGE="${optionBashBaseImage}" \ - -t "${imageTag}" \ - -t "${image}" \ - "${FRAMEWORK_ROOT_DIR}/.docker" - ) - - Log::displayInfo "Image ${image} - bash version check" - docker run --rm "${imageTag}" bash --version - - # shellcheck disable=SC2154 - if [[ "${optionPush}" = "1" ]]; then - Log::displayInfo "Push image ${image}" - ( - if [[ "${optionTraceVerbose}" = "1" ]]; then - set -x - fi - docker push "scrasnups/${imageTag}" - ) - 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 -Env::pathPrepend() { - local arg - for arg in "$@"; do - if [[ -d "${arg}" && ":${PATH}:" != *":${arg}:"* ]]; then - PATH="$(realpath "${arg}"):${PATH}" - fi - done -} - -# @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() { - if [[ "${LOAD_THEME:-1}" = "1" ]]; then - UI::theme "${BASH_FRAMEWORK_THEME-default}" - fi -} - -# @description get number of seconds since last modification of the file -# @arg $1 file:String file path -# @exitcode 1 if file does not exist -# @stdout number of seconds since last modification of the file -File::elapsedTimeSinceLastModification() { - local file="$1" - if [[ ! -f "${file}" ]]; then - return 1 - fi - local lastModificationTimeSeconds diff - lastModificationTimeSeconds="$(stat -c %Y "${file}")" - ((diff = $(date +%s) - lastModificationTimeSeconds)) - echo -n "${diff}" -} - -# FUNCTIONS - -facade_main_testsh() { -# REQUIRES -Env::requireLoad -UI::requireTheme -Log::requireLoad -Compiler::Facade::requireCommandBinDir - -# @require Compiler::Facade::requireCommandBinDir - -Bats::installRequirementsIfNeeded "${FRAMEWORK_ROOT_DIR}" - -declare -a BASH_FRAMEWORK_ARGV_FILTERED=() - -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() { - testCommand help - exit 0 -} - -# shellcheck disable=SC2317 # if function is overridden -optionVersionCallback() { - echo "${SCRIPT_NAME} version 1.0" - 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() { - 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}" - ;; - *) - Log::displayError "Command ${SCRIPT_NAME} - Invalid level ${level}" - return 1 - esac -} - -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}")" - 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 -} - -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$|\.tpl$|/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 - # 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 -} - -# shellcheck disable=SC2317 # if function is overridden -updateOptionVendorCallback() { - BASH_FRAMEWORK_ARGV_FILTERED+=("$1" "$2") -} - -# shellcheck disable=SC2317 # if function is overridden -updateOptionBashVersionCallback() { - BASH_FRAMEWORK_ARGV_FILTERED+=("$1" "$2") -} - -# shellcheck disable=SC2317 # if function is overridden -updateOptionBashBaseImageCallback() { - BASH_FRAMEWORK_ARGV_FILTERED+=("$1" "$2") -} - -# shellcheck disable=SC2317 # if function is overridden -updateOptionBranchNameCallback() { - BASH_FRAMEWORK_ARGV_FILTERED+=("$1" "$2") -} - -declare optionVendor="ubuntu" -declare optionBashVersion="5.1" -declare optionBashBaseImage="ubuntu:20.04" -declare optionContinuousIntegrationMode=0 - -# shellcheck disable=SC2317 # if function is overridden -updateOptionContinuousIntegrationMode() { - BASH_FRAMEWORK_ARGV_FILTERED+=("$1") -} - -declare optionSkipDockerBuild=0 - -# shellcheck disable=SC2317 # if function is overridden -updateOptionSkipDockerBuildCallback() { - BASH_FRAMEWORK_ARGV_FILTERED+=("$1") -} - -testCommand() { - local options_parse_cmd="$1" - shift || true - - if [[ "${options_parse_cmd}" = "parse" ]]; then - optionVendor="ubuntu" - local -i options_parse_optionParsedCountOptionVendor - ((options_parse_optionParsedCountOptionVendor = 0)) || true - optionBashVersion="5.1" - local -i options_parse_optionParsedCountOptionBashVersion - ((options_parse_optionParsedCountOptionBashVersion = 0)) || true - optionBashBaseImage="ubuntu:20.04" - local -i options_parse_optionParsedCountOptionBashBaseImage - ((options_parse_optionParsedCountOptionBashBaseImage = 0)) || true - local -i options_parse_optionParsedCountOptionBranchName - ((options_parse_optionParsedCountOptionBranchName = 0)) || true - optionContinuousIntegrationMode="0" - local -i options_parse_optionParsedCountOptionContinuousIntegrationMode - ((options_parse_optionParsedCountOptionContinuousIntegrationMode = 0)) || true - optionSkipDockerBuild="0" - local -i options_parse_optionParsedCountOptionSkipDockerBuild - ((options_parse_optionParsedCountOptionSkipDockerBuild = 0)) || true - local -i options_parse_optionParsedCountOptionBashFrameworkConfig - ((options_parse_optionParsedCountOptionBashFrameworkConfig = 0)) || true - optionConfig="0" - local -i options_parse_optionParsedCountOptionConfig - ((options_parse_optionParsedCountOptionConfig = 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 - optionNoColor="0" - local -i options_parse_optionParsedCountOptionNoColor - ((options_parse_optionParsedCountOptionNoColor = 0)) || true - optionTheme="default" - local -i options_parse_optionParsedCountOptionTheme - ((options_parse_optionParsedCountOptionTheme = 0)) || true - optionHelp="0" - local -i options_parse_optionParsedCountOptionHelp - ((options_parse_optionParsedCountOptionHelp = 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 - local -i options_parse_optionParsedCountOptionLogLevel - ((options_parse_optionParsedCountOptionLogLevel = 0)) || true - local -i options_parse_optionParsedCountOptionLogFile - ((options_parse_optionParsedCountOptionLogFile = 0)) || true - local -i options_parse_optionParsedCountOptionDisplayLevel - ((options_parse_optionParsedCountOptionDisplayLevel = 0)) || true - local -i options_parse_argParsedCountArgBats - ((options_parse_argParsedCountArgBats = 0)) || true - # 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/20 - # Option optionVendor --vendor variableType String min 0 max 1 authorizedValues 'alpine|ubuntu' regexp '' - --vendor) - shift - if (($# == 0)); then - Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - a value needs to be specified" - return 1 - fi - if [[ ! "$1" =~ alpine|ubuntu ]]; then - Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - value '$1' is not part of authorized values(alpine|ubuntu)" - return 1 - fi - if ((options_parse_optionParsedCountOptionVendor >= 1)); then - Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" - return 1 - fi - ((++options_parse_optionParsedCountOptionVendor)) - # shellcheck disable=SC2034 - optionVendor="$1" - updateOptionVendorCallback "${options_parse_arg}" "${optionVendor}" - # shellcheck disable=SC2317 - everyOptOrArgCallback "optionVendor" "${options_parse_arg}" "${optionVendor}" || true - ;; - # Option 2/20 - # Option optionBashVersion --bash-version variableType String min 0 max 1 authorizedValues '' regexp '' - --bash-version) - 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_optionParsedCountOptionBashVersion >= 1)); then - Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" - return 1 - fi - ((++options_parse_optionParsedCountOptionBashVersion)) - # shellcheck disable=SC2034 - optionBashVersion="$1" - updateOptionBashVersionCallback "${options_parse_arg}" "${optionBashVersion}" - # shellcheck disable=SC2317 - everyOptOrArgCallback "optionBashVersion" "${options_parse_arg}" "${optionBashVersion}" || true - ;; - # Option 3/20 - # Option optionBashBaseImage --bash-base-image variableType String min 0 max 1 authorizedValues '' regexp '' - --bash-base-image) - 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_optionParsedCountOptionBashBaseImage >= 1)); then - Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" - return 1 - fi - ((++options_parse_optionParsedCountOptionBashBaseImage)) - # shellcheck disable=SC2034 - optionBashBaseImage="$1" - updateOptionBashBaseImageCallback "${options_parse_arg}" "${optionBashBaseImage}" - # shellcheck disable=SC2317 - everyOptOrArgCallback "optionBashBaseImage" "${options_parse_arg}" "${optionBashBaseImage}" || true - ;; - # Option 4/20 - # Option optionBranchName --branch-name variableType String min 0 max 1 authorizedValues '' regexp '' - --branch-name) - 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_optionParsedCountOptionBranchName >= 1)); then - Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" - return 1 - fi - ((++options_parse_optionParsedCountOptionBranchName)) - # shellcheck disable=SC2034 - optionBranchName="$1" - updateOptionBranchNameCallback "${options_parse_arg}" "${optionBranchName}" - # shellcheck disable=SC2317 - everyOptOrArgCallback "optionBranchName" "${options_parse_arg}" "${optionBranchName}" || true - ;; - # Option 5/20 - # Option optionContinuousIntegrationMode --ci variableType Boolean min 0 max 1 authorizedValues '' regexp '' - --ci) - # shellcheck disable=SC2034 - optionContinuousIntegrationMode="1" - 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 - ((++options_parse_optionParsedCountOptionContinuousIntegrationMode)) - updateOptionContinuousIntegrationMode "${options_parse_arg}" - # shellcheck disable=SC2317 - everyOptOrArgCallback "optionContinuousIntegrationMode" "${options_parse_arg}" "${optionContinuousIntegrationMode}" || true - ;; - # Option 6/20 - # Option optionSkipDockerBuild --skip-docker-build variableType Boolean min 0 max 1 authorizedValues '' regexp '' - --skip-docker-build) - # shellcheck disable=SC2034 - optionSkipDockerBuild="1" - if ((options_parse_optionParsedCountOptionSkipDockerBuild >= 1)); then - Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" - return 1 - fi - ((++options_parse_optionParsedCountOptionSkipDockerBuild)) - updateOptionSkipDockerBuildCallback "${options_parse_arg}" - # shellcheck disable=SC2317 - everyOptOrArgCallback "optionSkipDockerBuild" "${options_parse_arg}" "${optionSkipDockerBuild}" || true - ;; - # Option 7/20 - # Option optionBashFrameworkConfig --bash-framework-config variableType String min 0 max 1 authorizedValues '' regexp '' - --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}" - # shellcheck disable=SC2317 - everyOptOrArgCallback "optionBashFrameworkConfig" "${options_parse_arg}" "${optionBashFrameworkConfig}" || true - ;; - # Option 8/20 - # Option optionConfig --config variableType Boolean min 0 max 1 authorizedValues '' regexp '' - --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)) - # shellcheck disable=SC2317 - everyOptOrArgCallback "optionConfig" "${options_parse_arg}" "${optionConfig}" || true - ;; - # Option 9/20 - # Option optionInfoVerbose --verbose|-v variableType Boolean min 0 max 1 authorizedValues '' regexp '' - --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}" - updateArgListInfoVerboseCallback "${options_parse_arg}" - # shellcheck disable=SC2317 - everyOptOrArgCallback "optionInfoVerbose" "${options_parse_arg}" "${optionInfoVerbose}" || true - ;; - # Option 10/20 - # Option optionDebugVerbose -vv variableType Boolean min 0 max 1 authorizedValues '' regexp '' - -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}" - updateArgListDebugVerboseCallback "${options_parse_arg}" - # shellcheck disable=SC2317 - everyOptOrArgCallback "optionDebugVerbose" "${options_parse_arg}" "${optionDebugVerbose}" || true - ;; - # Option 11/20 - # Option optionTraceVerbose -vvv variableType Boolean min 0 max 1 authorizedValues '' regexp '' - -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}" - updateArgListTraceVerboseCallback "${options_parse_arg}" - # shellcheck disable=SC2317 - everyOptOrArgCallback "optionTraceVerbose" "${options_parse_arg}" "${optionTraceVerbose}" || true - ;; - # Option 12/20 - # Option optionEnvFiles --env-file variableType StringArray min 0 max -1 authorizedValues '' regexp '' - --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[@]}" - # shellcheck disable=SC2317 - everyOptOrArgCallback "optionEnvFiles" "${options_parse_arg}" "${optionEnvFiles[@]}" || true - ;; - # Option 13/20 - # Option optionNoColor --no-color variableType Boolean min 0 max 1 authorizedValues '' regexp '' - --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}" - updateArgListNoColorCallback "${options_parse_arg}" - # shellcheck disable=SC2317 - everyOptOrArgCallback "optionNoColor" "${options_parse_arg}" "${optionNoColor}" || true - ;; - # Option 14/20 - # Option optionTheme --theme variableType String min 0 max 1 authorizedValues 'default|default-force|noColor' regexp '' - --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}" - # shellcheck disable=SC2317 - everyOptOrArgCallback "optionTheme" "${options_parse_arg}" "${optionTheme}" || true - ;; - # Option 15/20 - # Option optionHelp --help|-h variableType Boolean min 0 max 1 authorizedValues '' regexp '' - --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}" - # shellcheck disable=SC2317 - everyOptOrArgCallback "optionHelp" "${options_parse_arg}" "${optionHelp}" || true - ;; - # Option 16/20 - # Option optionVersion --version variableType Boolean min 0 max 1 authorizedValues '' regexp '' - --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}" - # shellcheck disable=SC2317 - everyOptOrArgCallback "optionVersion" "${options_parse_arg}" "${optionVersion}" || true - ;; - # Option 17/20 - # Option optionQuiet --quiet|-q variableType Boolean min 0 max 1 authorizedValues '' regexp '' - --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}" - updateArgListQuietCallback "${options_parse_arg}" - # shellcheck disable=SC2317 - everyOptOrArgCallback "optionQuiet" "${options_parse_arg}" "${optionQuiet}" || true - ;; - # Option 18/20 - # Option optionLogLevel --log-level variableType String min 0 max 1 authorizedValues 'OFF|ERR|ERROR|WARN|WARNING|INFO|DEBUG|TRACE' regexp '' - --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}" - # shellcheck disable=SC2317 - everyOptOrArgCallback "optionLogLevel" "${options_parse_arg}" "${optionLogLevel}" || true - ;; - # Option 19/20 - # Option optionLogFile --log-file variableType String min 0 max 1 authorizedValues '' regexp '' - --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}" - # shellcheck disable=SC2317 - everyOptOrArgCallback "optionLogFile" "${options_parse_arg}" "${optionLogFile}" || true - ;; - # Option 20/20 - # Option optionDisplayLevel --display-level variableType String min 0 max 1 authorizedValues 'OFF|ERR|ERROR|WARN|WARNING|INFO|DEBUG|TRACE' regexp '' - --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}" - # shellcheck disable=SC2317 - everyOptOrArgCallback "optionDisplayLevel" "${options_parse_arg}" "${optionDisplayLevel}" || true - ;; - -*) - # shellcheck disable=SC2317 - everyOptOrArgCallback "" "${options_parse_arg}" || argOptDefaultBehavior=$? - if [[ "${argOptDefaultBehavior}" = "0" ]]; then - Log::displayError "Command ${SCRIPT_NAME} - Invalid option ${options_parse_arg}" - return 1 - fi - ;; - *) - if ((0)); then - # Technical if - never reached - : - # Argument 1/1 - # Argument argBats min 0 max -1 authorizedValues '' regexp '' - elif ((options_parse_parsedArgIndex >= 0)); then - ((++options_parse_argParsedCountArgBats)) - # shellcheck disable=SC2034 - argBats+=("${options_parse_arg}") - everyOptOrArgCallback 'argBats' "${options_parse_arg}" || true - else - everyOptOrArgCallback '' "${options_parse_arg}" || argOptDefaultBehavior=$? - if [[ "${argOptDefaultBehavior}" = "0" ]]; then - Log::displayError "Command ${SCRIPT_NAME} - Argument - too much arguments provided: $*" - return 1 - fi - fi - ((++options_parse_parsedArgIndex)) - ;; - esac - shift || true - done - commandOptionParseFinished - testParseCallback - Log::displayDebug "Command ${SCRIPT_NAME} - parse arguments: ${BASH_FRAMEWORK_ARGV[*]}" - Log::displayDebug "Command ${SCRIPT_NAME} - parse filtered arguments: ${BASH_FRAMEWORK_ARGV_FILTERED[*]}" - elif [[ "${options_parse_cmd}" = "help" ]]; then - Array::wrap2 " " 80 0 "${__HELP_TITLE_COLOR}DESCRIPTION:${__RESET_COLOR}" "Launch bats inside docker container" - echo - - echo -e "$(Array::wrap2 " " 80 2 "${__HELP_TITLE_COLOR}USAGE:${__RESET_COLOR}" "${SCRIPT_NAME}" "[OPTIONS]" "[ARGUMENTS]")" - echo -e "$(Array::wrap2 " " 80 2 "${__HELP_TITLE_COLOR}USAGE:${__RESET_COLOR}" \ - "${SCRIPT_NAME}" \ - "[--vendor ]" "[--bash-version ]" "[--bash-base-image ]" "[--branch-name ]" "[--ci]" "[--skip-docker-build]" "[--bash-framework-config ]" "[--config]" "[--verbose|-v]" "[-vv]" "[-vvv]" "[--env-file ]" "[--no-color]" "[--theme ]" "[--help|-h]" "[--version]" "[--quiet|-q]" "[--log-level ]" "[--log-file ]" "[--display-level ]")" - echo - echo -e "${__HELP_TITLE_COLOR}ARGUMENTS:${__RESET_COLOR}" - echo -e " [${__HELP_OPTION_COLOR}batsArgs${__HELP_NORMAL} {list} (optional)]" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(options\ passed\ to\ bats\ command) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo - echo -e "${__HELP_TITLE_COLOR}OPTIONS:${__RESET_COLOR}" - echo -e " ${__HELP_OPTION_COLOR}--vendor ${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(vendor\ image\ to\ use:\ alpine\|ubuntu) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo ' Default value: ubuntu' - echo ' Possible values: alpine|ubuntu' - echo -e " ${__HELP_OPTION_COLOR}--bash-version ${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(version\ of\ bash\ to\ use:\ 4.4\|5.0\|5.1\|5.2) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo ' Default value: 5.1' - echo -e " ${__HELP_OPTION_COLOR}--bash-base-image ${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(bash\ bash\ image\ to\ use\ \(eg:\ ubuntu:20.04\,\ amd64/bash:4.4-alpine3.18\)) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo ' Default value: ubuntu:20.04' - echo -e " ${__HELP_OPTION_COLOR}--branch-name ${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(branch\ name\ being\ built\,\ will\ help\ to\ create\ docker\ image\ tag\ name) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo -e " ${__HELP_OPTION_COLOR}--ci${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(activate\ continuous\ integration\ mode\ \(tmp\ folder\ not\ shared\ with\ host\)) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo -e " ${__HELP_OPTION_COLOR}--skip-docker-build${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(skip\ docker\ image\ build\ if\ option\ provided) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo - echo -e "${__HELP_TITLE_COLOR}GLOBAL OPTIONS:${__RESET_COLOR}" - echo -e " ${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(use\ alternate\ bash\ framework\ configuration.) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo -e " ${__HELP_OPTION_COLOR}--config${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(Display\ configuration) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo -e " ${__HELP_OPTION_COLOR}--verbose${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-v${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(info\ level\ verbose\ mode\ \(alias\ of\ --display-level\ INFO\)) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo -e " ${__HELP_OPTION_COLOR}-vv${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(debug\ level\ verbose\ mode\ \(alias\ of\ --display-level\ DEBUG\)) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo -e " ${__HELP_OPTION_COLOR}-vvv${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(trace\ level\ verbose\ mode\ \(alias\ of\ --display-level\ TRACE\)) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(Load\ the\ specified\ env\ file\ \(deprecated\,\ please\ use\ --bash-framework-config\ option\ instead\)) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(Produce\ monochrome\ output.\ alias\ of\ --theme\ noColor.) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo -e " ${__HELP_OPTION_COLOR}--theme ${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(choose\ color\ theme\ -\ default-force\ means\ colors\ will\ be\ produced\ even\ if\ command\ is\ piped) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo ' Default value: default' - echo ' Possible values: default|default-force|noColor' - echo -e " ${__HELP_OPTION_COLOR}--help${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-h${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(Display\ this\ command\ help) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo -e " ${__HELP_OPTION_COLOR}--version${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(Print\ version\ information\ and\ quit) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo -e " ${__HELP_OPTION_COLOR}--quiet${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-q${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(quiet\ mode\,\ doesn\'t\ display\ any\ output) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo -e " ${__HELP_OPTION_COLOR}--log-level ${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(Set\ log\ level) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo ' Possible values: OFF|ERR|ERROR|WARN|WARNING|INFO|DEBUG|TRACE' - echo -e " ${__HELP_OPTION_COLOR}--log-file ${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(Set\ log\ file) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo -e " ${__HELP_OPTION_COLOR}--display-level ${__HELP_NORMAL} {single}" - local -a helpArray - # shellcheck disable=SC2054 - helpArray=(set\ display\ level) - echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo ' Possible values: OFF|ERR|ERROR|WARN|WARNING|INFO|DEBUG|TRACE' - echo -e """ -Launch bats inside docker container - -${__HELP_TITLE}Examples:${__HELP_NORMAL} -to force image rebuilding - DOCKER_BUILD_OPTIONS=--no-cache ./bin/test - -rebuild alpine image - DOCKER_BUILD_OPTIONS=--no-cache VENDOR=alpine BASH_IMAGE=bash:5.1 BASH_TAR_VERSION=5.1 ./bin/test - -${__HELP_TITLE}In order to debug inside container:${__HELP_NORMAL} - docker build -t bash-tools-ubuntu:5.1 -f .docker/Dockerfile.ubuntu - docker run --rm -it -v \"\$(pwd):/bash\" --user \"\$(id -u):\$(id -g)\" bash-tools-ubuntu-5.1-user bash - docker run --rm -it -v \"\$(pwd):/bash\" --user \"\$(id -u):\$(id -g)\" bash-tools-alpine-5.1-user bash - -${__HELP_TITLE}Bats help:${__HELP_NORMAL} -@@@BATS_HELP@@@""" - echo - echo -n -e "${__HELP_TITLE_COLOR}VERSION: ${__RESET_COLOR}" - echo '1.0' - echo - echo -e "${__HELP_TITLE_COLOR}AUTHOR:${__RESET_COLOR}" - echo '[François Chastanet](https://github.com/fchastanet)' - echo - echo -e "${__HELP_TITLE_COLOR}SOURCE FILE:${__RESET_COLOR}" - echo 'https://github.com/fchastanet/bash-tools-framework/tree/master/src/_binaries/test.sh' - echo - echo -e "${__HELP_TITLE_COLOR}LICENSE:${__RESET_COLOR}" - echo 'MIT License' - echo - Array::wrap2 ' ' 76 4 "$(copyrightCallback)" - else - Log::displayError "Command ${SCRIPT_NAME} - Option command invalid: '${options_parse_cmd}'" - return 1 - fi -} -declare copyrightBeginYear="2022" -declare -a batsArgs=() -everyOptOrArgCallback() { - if [[ -z "$1" || "$1" = "argBats" ]]; then - batsArgs+=("$2") - return 1 - fi -} - -testParseCallback() { - if ((${#batsArgs} == 0)); then - batsArgs=(-r src) - fi -} - -optionHelpCallback() { - local batsHelpFile - batsHelpFile="$(Framework::createTempFile "batsHelp")" - "${FRAMEWORK_VENDOR_DIR}/bats/bin/bats" --help >"${batsHelpFile}" - - testCommand help | - sed -E \ - -e "/@@@BATS_HELP@@@/r ${batsHelpFile}" \ - -e "/@@@BATS_HELP@@@/d" - exit 0 -} - -optionVersionCallback() { - local batsVersion - echo -e "${__HELP_TITLE_COLOR}${SCRIPT_NAME} version: ${__RESET_COLOR} 1.0" - batsVersion="$("${FRAMEWORK_VENDOR_DIR}/bats/bin/bats" --version)" - echo -e "${__HELP_TITLE_COLOR}bats version: ${__RESET_COLOR} ${batsVersion}" - exit 0 -} - -getBatsLevel() { - local levelName="$1" - case "${levelName^^}" in - TRACE) echo "--trace" ;; - DEBUG) echo "--verbose-run" ;; - *) - # ignore - return 1 - esac -} - -updateArgListLogLevelCallback() { - local level="$2" - batsLevel="$(getBatsLevel "${level}")" || return 0 - batsArgs+=("${batsLevel}") -} - -updateArgListDisplayLevelCallback() { - local level="$2" - if [[ "${IN_BASH_DOCKER:-}" = "You're in docker" ]]; then - batsLevel="$(getBatsLevel "${level}")" || return 0 - batsArgs+=("${batsLevel}") - else - batsArgs+=("$1" "${level}") - fi -} - -updateArgListDebugVerboseCallback() { - if [[ "${IN_BASH_DOCKER:-}" = "You're in docker" ]]; then - batsArgs+=("--verbose-run") - else - batsArgs+=("-vv") - fi -} -updateArgListTraceVerboseCallback() { - if [[ "${IN_BASH_DOCKER:-}" = "You're in docker" ]]; then - batsArgs+=("--trace") - else - batsArgs+=("-vvv") - fi -} - -addOptionsSpecificToRunBuildContainer() { - if [[ "${IN_BASH_DOCKER:-}" != "You're in docker" ]]; then - batsArgs+=("${@}") - fi -} -updateOptionBashVersionCallback() { - addOptionsSpecificToRunBuildContainer "$@" -} - -updateOptionVendorCallback() { - addOptionsSpecificToRunBuildContainer "$@" -} - -updateOptionBashBaseImageCallback() { - addOptionsSpecificToRunBuildContainer "$@" -} - -updateOptionBranchNameCallback() { - addOptionsSpecificToRunBuildContainer "$@" -} - -updateOptionContinuousIntegrationMode() { - addOptionsSpecificToRunBuildContainer "$@" -} - -updateOptionSkipDockerBuildCallback() { - addOptionsSpecificToRunBuildContainer "$@" -} - -testCommand parse "${BASH_FRAMEWORK_ARGV[@]}" - -# shellcheck disable=SC2154 -run() { - if [[ "${IN_BASH_DOCKER:-}" = "You're in docker" ]]; then - ( - "${FRAMEWORK_VENDOR_DIR}/bats/bin/bats" "${batsArgs[@]}" - ) - else - # shellcheck disable=SC2034 - local -a dockerRunCmd=( - "/bash/bin/test" - "${batsArgs[@]}" - ) - # shellcheck disable=SC2034 - local -a dockerRunArgs - if [[ "$0" =~ ^/ ]]; then - dockerRunArgs+=( - -v "$0:/bash/bin/test" - ) - else - dockerRunArgs+=( - -v "$(pwd -P)/$0:/bash/bin/test" - ) - fi - - # shellcheck disable=SC2154 - Docker::runBuildContainer \ - "${optionVendor}" \ - "${optionBashVersion}" \ - "${optionBashBaseImage}" \ - "${optionSkipDockerBuild}" \ - "${optionTraceVerbose}" \ - "${optionContinuousIntegrationMode}" \ - dockerRunCmd \ - dockerRunArgs - fi -} - -if [[ "${BASH_FRAMEWORK_QUIET_MODE:-0}" = "1" ]]; then - run &>/dev/null -else - run -fi - -} - -facade_main_testsh "$@" diff --git a/cspell.yaml b/cspell.yaml index 985739e0..040f48c1 100644 --- a/cspell.yaml +++ b/cspell.yaml @@ -56,16 +56,9 @@ dictionaryDefinitions: - name: lintersConfig path: ".cspell/lintersConfig.txt" addWords: true - -dictionaries: - - bash - - plantuml - - myAwk - - myBash - - loremIpsum - - config - - softwares - - softwareTerms + - name: myAwk + path: ".cspell/myAwk.txt" + addWords: false # https://github.com/streetsidesoftware/cspell/blob/main/packages/cspell/README.md#languagesettings languageSettings: @@ -93,6 +86,7 @@ languageSettings: overrides: - filename: - "**/*.{bats,tpl}" + - "**/*.env" - "src/Array/wrap.sh" languageId: shellscript dictionaries: diff --git a/doc/templates/Commands.tmpl.md b/doc/templates/Commands.tmpl.md index cac533c1..301001cc 100644 --- a/doc/templates/Commands.tmpl.md +++ b/doc/templates/Commands.tmpl.md @@ -8,10 +8,8 @@ - [2. build tools](#2-build-tools) - [2.1. bin/installRequirements](#21-bininstallrequirements) - [2.2. bin/findShebangFiles](#22-binfindshebangfiles) - - [2.3. bin/test](#23-bintest) - - [2.4. bin/runBuildContainer](#24-binrunbuildcontainer) - - [2.5. bin/buildPushDockerImage](#25-binbuildpushdockerimage) - - [2.6. bin/doc](#26-bindoc) + - [2.3. bin/buildPushDockerImage](#23-binbuildpushdockerimage) + - [2.4. bin/doc](#24-bindoc) - [3. Linters](#3-linters) - [3.1. bin/frameworkLint](#31-binframeworklint) - [3.2. bin/dockerLint](#32-bindockerlint) @@ -54,25 +52,13 @@ Here the command's help output: @@@findShebangFiles_help@@@ ``` -### 2.3. bin/test - -```text -@@@test_help@@@ -``` - -### 2.4. bin/runBuildContainer - -```text -@@@runBuildContainer_help@@@ -``` - -### 2.5. bin/buildPushDockerImage +### 2.3. bin/buildPushDockerImage ```text @@@buildPushDockerImage_help@@@ ``` -### 2.6. bin/doc +### 2.4. bin/doc ```text @@@doc_help@@@ diff --git a/preCommitTest.sh b/preCommitTest.sh new file mode 100755 index 00000000..b7e2babd --- /dev/null +++ b/preCommitTest.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +if [[ ! -f "$(pwd)/test.sh" ]]; then + echo >&2 "test.sh does not exist in this repository" + exit 1 +fi + +exec "$(pwd)/test.sh" "$@" diff --git a/src/Dns/checkHostname.bats b/src/Dns/checkHostname.bats index e1153756..4925cf13 100755 --- a/src/Dns/checkHostname.bats +++ b/src/Dns/checkHostname.bats @@ -48,7 +48,7 @@ function Dns::checkHostnameExternalHostLinux { #@test assert_line --index 0 --partial "INFO - try to reach host willywonka.fchastanet.lan" assert_line --index 1 --partial "PING willywonka.fchastanet.lan (192.168.1.1) 56(84) bytes of data." assert_line --index 2 --partial "INFO - check if ip(192.168.1.1) associated to host(willywonka.fchastanet.lan) is listed in your network configuration" - assert_line --index 3 --partial "inet 192.168.1.1 netmask 255.255.0.0 broadcast 192.168.255.255" + assert_line --index 3 --partial "net 192.168.1.1 netmask 255.255.0.0 broadcast 192.168.255.255" } function Dns::checkHostnameExternalHostWindows { #@test diff --git a/src/Dns/testsData/ifconfig.txt b/src/Dns/testsData/ifconfig.txt index ae5dfb1d..9563680a 100644 --- a/src/Dns/testsData/ifconfig.txt +++ b/src/Dns/testsData/ifconfig.txt @@ -1,2 +1,2 @@ eth4: flags=4163 mtu 1500 -inet 192.168.1.1 netmask 255.255.0.0 broadcast 192.168.255.255 +net 192.168.1.1 netmask 255.255.0.0 broadcast 192.168.255.255 diff --git a/src/Docker/runBuildContainer.sh b/src/Docker/runBuildContainer.sh deleted file mode 100755 index 16d83191..00000000 --- a/src/Docker/runBuildContainer.sh +++ /dev/null @@ -1,98 +0,0 @@ -#!/bin/bash - -# @description run the container specified by args provided. -# build and push the image if needed -# -# @env DOCKER_BUILD_OPTIONS -# @env SKIP_USER -# @env USER_ID -# @env GROUP_ID -# @env FRAMEWORK_ROOT_DIR -# @env DOCKER_OPTION_IMAGE_TAG -# @env BASH_FRAMEWORK_ARGV_FILTERED -Docker::runBuildContainer() { - local optionVendor="$1" - local optionBashVersion="$2" - local optionBashBaseImage="$3" - local optionSkipDockerBuild="$4" - local optionTraceVerbose="$5" - local optionContinuousIntegrationMode="$6" - local -n localDockerRunCmd=$7 - local -n localDockerRunArgs=$8 - - if [[ -d "$(pwd)/vendor/bash-tools-framework" ]]; then - localDockerRunArgs+=( - -v "$(cd "$(pwd)/vendor/bash-tools-framework" && pwd -P):/bash/vendor/bash-tools-framework" - ) - fi - - # shellcheck disable=SC2154 - if [[ "${optionContinuousIntegrationMode}" = "0" ]]; then - localDockerRunArgs+=(-v "/tmp:/tmp") - fi - localDockerRunArgs+=(-e KEEP_TEMP_FILES="${KEEP_TEMP_FILES}") - localDockerRunArgs+=(-e BATS_FIX_TEST="${BATS_FIX_TEST:-0}") - - # shellcheck disable=SC2154 - Log::displayInfo "Using ${optionVendor}:${optionBashVersion}" - - local imageRef="${DOCKER_OPTION_IMAGE_TAG:-build:bash-tools-${optionVendor}-${optionBashVersion}}" - if [[ "${optionSkipDockerBuild:-0}" != "1" ]]; then - Log::displayInfo "Build docker image ${imageRef}" - # shellcheck disable=SC2154 - ( - if [[ "${optionTraceVerbose}" = "1" ]]; then - set -x - fi - Docker::buildPushDockerImage \ - "${optionVendor}" \ - "${optionBashVersion}" \ - "${optionBashBaseImage}" \ - "${optionPush}" \ - "${optionTraceVerbose}" - ) - fi - if [[ -f "${FRAMEWORK_ROOT_DIR}/.docker/DockerfileUser" ]]; then - local imageRefUser="${imageRef}-user" - if [[ "${optionSkipDockerBuild:-0}" != "1" ]]; then - Log::displayInfo "build docker image ${imageRefUser} with user configuration" - # shellcheck disable=SC2154 - ( - if [[ "${optionTraceVerbose}" = "1" ]]; then - set -x - fi - # shellcheck disable=SC2086 - DOCKER_BUILDKIT=1 docker build \ - ${DOCKER_BUILD_OPTIONS} \ - --cache-from "scrasnups/${imageRef}" \ - --build-arg "BASH_IMAGE=scrasnups/${imageRef}" \ - --build-arg SKIP_USER="${SKIP_USER:-0}" \ - --build-arg USER_ID="${USER_ID:-$(id -u)}" \ - --build-arg GROUP_ID="${GROUP_ID:-$(id -g)}" \ - -f "${FRAMEWORK_ROOT_DIR}/.docker/DockerfileUser" \ - -t "${imageRefUser}" \ - "${FRAMEWORK_ROOT_DIR}/.docker" - ) - fi - fi - - Log::displayDebug "Run container with localDockerRunCmd: ${localDockerRunCmd[*]}" - Log::displayDebug "Run container with localDockerRunArgs: ${localDockerRunArgs[*]}" - Log::displayDebug "Run container with BASH_FRAMEWORK_ARGV_FILTERED: ${BASH_FRAMEWORK_ARGV_FILTERED[*]}" - ( - # shellcheck disable=SC2154 - if [[ "${optionTraceVerbose}" = "1" ]]; then - set -x - fi - # shellcheck disable=SC2086 - docker run \ - --rm \ - "${localDockerRunArgs[@]}" \ - ${DOCKER_RUN_OPTIONS} \ - -w /bash \ - -v "$(pwd):/bash" \ - --user "${USER_ID:-$(id -u)}:${GROUP_ID:-$(id -g)}" \ - "${imageRefUser}" \ - "${localDockerRunCmd[@]}" - ) -} diff --git a/src/_binaries/doc.sh b/src/_binaries/doc.sh index 6bbc8ce2..81d506bd 100755 --- a/src/_binaries/doc.sh +++ b/src/_binaries/doc.sh @@ -7,35 +7,48 @@ docCommand parse "${BASH_FRAMEWORK_ARGV[@]}" -run() { - PAGES_DIR="${FRAMEWORK_ROOT_DIR}/pages" +runContainer() { + local image="scrasnups/build:bash-tools-ubuntu-5.3" + local -a dockerRunCmd=( + "/bash/bin/doc" + "${BASH_FRAMEWORK_ARGV_FILTERED[@]}" + ) - if [[ "${IN_BASH_DOCKER:-}" != "You're in docker" ]]; then - ShellDoc::installRequirementsIfNeeded - Softwares::installHadolint - Softwares::installShellcheck - - # shellcheck disable=SC2034 - 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" ) - # shellcheck disable=SC2034 - local -a dockerArgvFiltered=() - # shellcheck disable=SC2154 - Docker::runBuildContainer \ - "${optionVendor:-ubuntu}" \ - "${optionBashVersion:-5.1}" \ - "${optionBashBaseImage:-ubuntu:20.04}" \ - "${optionSkipDockerBuild}" \ - "${optionTraceVerbose}" \ - "${optionContinuousIntegrationMode}" \ - dockerRunCmd \ - dockerArgvFiltered - - exit $? 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 #----------------------------- @@ -106,6 +119,21 @@ run() { fi } +installRequirements() { + ShellDoc::installRequirementsIfNeeded + Softwares::installHadolint + Softwares::installShellcheck +} + +run() { + if [[ "${IN_BASH_DOCKER:-}" != "You're in docker" ]]; then + installRequirements + runContainer + else + generateDoc + fi +} + if [[ "${BASH_FRAMEWORK_QUIET_MODE:-0}" = "1" ]]; then run &>/dev/null else diff --git a/src/_binaries/options/command.doc.tpl b/src/_binaries/options/command.doc.tpl index 0feda4af..fab4b6c3 100644 --- a/src/_binaries/options/command.doc.tpl +++ b/src/_binaries/options/command.doc.tpl @@ -1,6 +1,6 @@ % declare generateSkipDockerBuildOption=1 -declare versionNumber="1.0" +declare versionNumber="1.1" declare commandFunctionName="docCommand" declare help="Generate markdown documentation." declare longDescription=""" @@ -8,14 +8,8 @@ INTERNAL TOOL """ % .INCLUDE "$(dynamicTemplateDir _binaries/options/options.base.tpl)" -.INCLUDE "$(dynamicTemplateDir _binaries/options/options.bashFrameworkDockerImage.tpl)" .INCLUDE "$(dynamicTemplateDir _binaries/options/options.ci.tpl)" -.INCLUDE "$(dynamicTemplateDir _binaries/options/options.skipDockerBuild.tpl)" % Options::generateCommand "${options[@]}" % declare copyrightBeginYear="2022" - -readonly defaultVendor="ubuntu" -readonly defaultBashVersion="5.1" -readonly defaultBashBaseImage="ubuntu:20.04" diff --git a/src/_binaries/options/command.runBuildContainer.tpl b/src/_binaries/options/command.runBuildContainer.tpl deleted file mode 100644 index 0a987183..00000000 --- a/src/_binaries/options/command.runBuildContainer.tpl +++ /dev/null @@ -1,47 +0,0 @@ -% -declare generateSkipDockerBuildOption=1 -declare versionNumber="1.0" -declare commandFunctionName="runBuildContainerCommand" -declare help="run the container eventually building the docker image before." -# shellcheck disable=SC2016 -declare longDescription=''' -run the container specified by args provided. -Command to run is passed via the rest of arguments. -TTY allocation is detected automatically. - -additional env variables can be passed to docker build: - - ${__HELP_OPTION_COLOR}SKIP_USER${__HELP_NORMAL} (default: 0) - - ${__HELP_OPTION_COLOR}USER_ID${__HELP_NORMAL} (default: current user id provided by (id -u) command) - - ${__HELP_OPTION_COLOR}GROUP_ID${__HELP_NORMAL} (default: current group id provided by (id -g) command) - -additional docker run options can be passed - via ${__HELP_OPTION_COLOR}DOCKER_RUN_OPTIONS${__HELP_NORMAL} env variable -''' -% -.INCLUDE "$(dynamicTemplateDir _binaries/options/options.base.tpl)" -.INCLUDE "$(dynamicTemplateDir _binaries/options/options.bashFrameworkDockerImage.tpl)" -.INCLUDE "$(dynamicTemplateDir _binaries/options/options.ci.tpl)" -.INCLUDE "$(dynamicTemplateDir _binaries/options/options.skipDockerBuild.tpl)" -% -# shellcheck source=/dev/null -source <( - Options::generateArg \ - --variable-name "dockerRunCmd" \ - --min 0 \ - --max -1 \ - --name "arg" \ - --help "command arguments including command name" \ - --function-name dockerRunCmdFunction -) -options+=( - --unknown-option-callback unknownOption - dockerRunCmdFunction -) -Options::generateCommand "${options[@]}" -% -declare copyrightBeginYear="2022" - -# shellcheck disable=SC2317 # if function is overridden -unknownOption() { - dockerRunCmd+=("$1") -} diff --git a/src/_binaries/options/command.shellcheckLint.tpl b/src/_binaries/options/command.shellcheckLint.tpl index 8808a652..e80d7c7d 100644 --- a/src/_binaries/options/command.shellcheckLint.tpl +++ b/src/_binaries/options/command.shellcheckLint.tpl @@ -88,13 +88,9 @@ argShellcheckFilesCallback() { } shellcheckLintParseCallback() { if [[ "${optionStaged}" = "1" ]] && ((${#argShellcheckFiles[@]} > 0)); then - Log::displayWarning "${SCRIPT_NAME} - --staged option ignored as files as been provided" + Log::displayWarning "${SCRIPT_NAME} - --staged option ignored as files have been provided" optionStaged="0" fi - if [[ "${optionFormat}" != "tty" && "${optionXargs}" = "1" ]]; then - Log::displayWarning "--xargs option ignored as only supported with tty format" - optionXargs="0" - fi shellcheckArgs=(-f "${optionFormat}") } diff --git a/src/_binaries/options/command.test.tpl b/src/_binaries/options/command.test.tpl deleted file mode 100644 index 9e67eb2b..00000000 --- a/src/_binaries/options/command.test.tpl +++ /dev/null @@ -1,155 +0,0 @@ -% -declare versionNumber="1.0" -declare commandFunctionName="testCommand" -declare help="Launch bats inside docker container" -# shellcheck disable=SC2016 -declare longDescription=''' -Launch bats inside docker container - -${__HELP_TITLE}Examples:${__HELP_NORMAL} -to force image rebuilding - DOCKER_BUILD_OPTIONS=--no-cache ./bin/test - -rebuild alpine image - DOCKER_BUILD_OPTIONS=--no-cache VENDOR=alpine BASH_IMAGE=bash:5.1 BASH_TAR_VERSION=5.1 ./bin/test - -${__HELP_TITLE}In order to debug inside container:${__HELP_NORMAL} - docker build -t bash-tools-ubuntu:5.1 -f .docker/Dockerfile.ubuntu - docker run --rm -it -v \"\$(pwd):/bash\" --user \"\$(id -u):\$(id -g)\" bash-tools-ubuntu-5.1-user bash - docker run --rm -it -v \"\$(pwd):/bash\" --user \"\$(id -u):\$(id -g)\" bash-tools-alpine-5.1-user bash - -${__HELP_TITLE}Bats help:${__HELP_NORMAL} -@@@BATS_HELP@@@ -''' -% - -.INCLUDE "$(dynamicTemplateDir _binaries/options/options.base.tpl)" -.INCLUDE "$(dynamicTemplateDir _binaries/options/options.bashFrameworkDockerImage.tpl)" -.INCLUDE "$(dynamicTemplateDir _binaries/options/options.ci.tpl)" -.INCLUDE "$(dynamicTemplateDir _binaries/options/options.skipDockerBuild.tpl)" - -% -# shellcheck source=/dev/null -source <( - Options::generateArg \ - --variable-name "argBats" \ - --min 0 \ - --max -1 \ - --name "batsArgs" \ - --help "options passed to bats command" \ - --function-name argBatsFunction -) -options+=( - --callback testParseCallback - --every-option-callback everyOptOrArgCallback - --every-argument-callback everyOptOrArgCallback - argBatsFunction -) -Options::generateCommand "${options[@]}" -% -declare copyrightBeginYear="2022" -declare -a batsArgs=() -everyOptOrArgCallback() { - if [[ -z "$1" || "$1" = "argBats" ]]; then - batsArgs+=("$2") - return 1 - fi -} - -testParseCallback() { - if ((${#batsArgs} == 0)); then - batsArgs=(-r src) - fi -} - -optionHelpCallback() { - local batsHelpFile - batsHelpFile="$(Framework::createTempFile "batsHelp")" - "${FRAMEWORK_VENDOR_DIR}/bats/bin/bats" --help >"${batsHelpFile}" - - <% ${commandFunctionName} %> help | - sed -E \ - -e "/@@@BATS_HELP@@@/r ${batsHelpFile}" \ - -e "/@@@BATS_HELP@@@/d" - exit 0 -} - - -optionVersionCallback() { - local batsVersion - echo -e "${__HELP_TITLE_COLOR}${SCRIPT_NAME} version: ${__RESET_COLOR} <% ${versionNumber} %>" - batsVersion="$("${FRAMEWORK_VENDOR_DIR}/bats/bin/bats" --version)" - echo -e "${__HELP_TITLE_COLOR}bats version: ${__RESET_COLOR} ${batsVersion}" - exit 0 -} - -getBatsLevel() { - local levelName="$1" - case "${levelName^^}" in - TRACE) echo "--trace" ;; - DEBUG) echo "--verbose-run" ;; - *) - # ignore - return 1 - esac -} - -updateArgListLogLevelCallback() { - local level="$2" - batsLevel="$(getBatsLevel "${level}")" || return 0 - batsArgs+=("${batsLevel}") -} - -updateArgListDisplayLevelCallback() { - local level="$2" - if [[ "${IN_BASH_DOCKER:-}" = "You're in docker" ]]; then - batsLevel="$(getBatsLevel "${level}")" || return 0 - batsArgs+=("${batsLevel}") - else - batsArgs+=("$1" "${level}") - fi -} - -updateArgListDebugVerboseCallback() { - if [[ "${IN_BASH_DOCKER:-}" = "You're in docker" ]]; then - batsArgs+=("--verbose-run") - else - batsArgs+=("-vv") - fi -} -updateArgListTraceVerboseCallback() { - if [[ "${IN_BASH_DOCKER:-}" = "You're in docker" ]]; then - batsArgs+=("--trace") - else - batsArgs+=("-vvv") - fi -} - -addOptionsSpecificToRunBuildContainer() { - if [[ "${IN_BASH_DOCKER:-}" != "You're in docker" ]]; then - batsArgs+=("${@}") - fi -} -updateOptionBashVersionCallback() { - addOptionsSpecificToRunBuildContainer "$@" -} - -updateOptionVendorCallback() { - addOptionsSpecificToRunBuildContainer "$@" -} - -updateOptionBashBaseImageCallback() { - addOptionsSpecificToRunBuildContainer "$@" -} - -updateOptionBranchNameCallback() { - addOptionsSpecificToRunBuildContainer "$@" -} - -updateOptionContinuousIntegrationMode() { - addOptionsSpecificToRunBuildContainer "$@" -} - -updateOptionSkipDockerBuildCallback() { - addOptionsSpecificToRunBuildContainer "$@" -} diff --git a/src/_binaries/runBuildContainer.sh b/src/_binaries/runBuildContainer.sh deleted file mode 100755 index 6f6230b2..00000000 --- a/src/_binaries/runBuildContainer.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env bash -# BIN_FILE=${FRAMEWORK_ROOT_DIR}/bin/runBuildContainer -# VAR_RELATIVE_FRAMEWORK_DIR_TO_CURRENT_DIR=.. -# FACADE - -.INCLUDE "$(dynamicTemplateDir _binaries/options/command.runBuildContainer.tpl)" - -# shellcheck disable=SC2034 -declare -a dockerRunCmd=() -# shellcheck disable=SC2034 -declare -a dockerRunArgs=() -export DOCKER_BUILD_OPTIONS="${DOCKER_BUILD_OPTIONS:-}" -export DOCKER_RUN_OPTIONS="${DOCKER_RUN_OPTIONS:-}" -export BASH_FRAMEWORK_ROOT_DIR="${FRAMEWORK_ROOT_DIR}" - -runBuildContainerCommand parse "${BASH_FRAMEWORK_ARGV[@]}" - -run() { - # shellcheck disable=SC2154 - Docker::runBuildContainer \ - "${optionVendor}" \ - "${optionBashVersion}" \ - "${optionBashBaseImage}" \ - "${optionSkipDockerBuild}" \ - "${optionTraceVerbose}" \ - "${optionContinuousIntegrationMode}" \ - dockerRunCmd \ - dockerRunArgs -} - -if [[ "${BASH_FRAMEWORK_QUIET_MODE:-0}" = "1" ]]; then - run &>/dev/null -else - run -fi diff --git a/src/_binaries/shellcheckLint.sh b/src/_binaries/shellcheckLint.sh index 81a7325d..8df63552 100755 --- a/src/_binaries/shellcheckLint.sh +++ b/src/_binaries/shellcheckLint.sh @@ -3,11 +3,6 @@ # VAR_RELATIVE_FRAMEWORK_DIR_TO_CURRENT_DIR=.. # FACADE -# check if command in PATH is already the minimal version needed -if ! Version::checkMinimal "${FRAMEWORK_VENDOR_BIN_DIR}/shellcheck" "--version" "${MIN_SHELLCHECK_VERSION}" >/dev/null 2>&1; then - Softwares::installShellcheck -fi - .INCLUDE "$(dynamicTemplateDir _binaries/options/command.shellcheckLint.tpl)" shellcheckLintCommand parse "${BASH_FRAMEWORK_ARGV[@]}" @@ -82,13 +77,31 @@ run() { if [[ "${optionTraceVerbose}" = "1" ]]; then xargsArgs+=(-t) fi - + local tmpDir + tmpDir="$(mktemp -d -p "${TMPDIR:-/tmp}" -t bash-tools-shellcheck-XXXXXX)" + export tmpDir + # shellcheck disable=SC2016 echo "${files[@]}" | optionTraceVerbose="${optionTraceVerbose}" \ shellcheckArgsStr="${shellcheckArgs[*]}" \ xargs "${xargsArgs[@]}" \ - bash -c 'shellcheckFunction $@' bash - + bash -c 'echo >&2 "linting $* ..."; file="$(md5sum <<<"$@")"; shellcheckFunction $@ | tee "${tmpDir}/${file%% *}" >&2; echo >&2 "linted $*"' bash + if [[ "${optionFormat}" = "checkstyle" ]]; then + ( + echo "" + echo "" + awk '//{flag=0} flag' "${tmpDir}/"* + echo "" + ) + elif [[ "${optionFormat}" = "json" ]]; then + ( + jq '.[]' "${tmpDir}/"* | jq -s '.' + ) + elif [[ "${optionFormat}" = "json1" ]]; then + jq -s 'map(.comments[])' "${tmpDir}/"* | jq '{"comments": .}' + else + cat "${tmpDir}/"* + fi ) else Log::displayWarning "no file provided" diff --git a/src/_binaries/test.sh b/src/_binaries/test.sh deleted file mode 100755 index faa0949e..00000000 --- a/src/_binaries/test.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env bash -# BIN_FILE=${FRAMEWORK_ROOT_DIR}/bin/test -# FACADE - -Bats::installRequirementsIfNeeded "${FRAMEWORK_ROOT_DIR}" - -.INCLUDE "$(dynamicTemplateDir _binaries/options/command.test.tpl)" - -testCommand parse "${BASH_FRAMEWORK_ARGV[@]}" - -# shellcheck disable=SC2154 -run() { - if [[ "${IN_BASH_DOCKER:-}" = "You're in docker" ]]; then - ( - "${FRAMEWORK_VENDOR_DIR}/bats/bin/bats" "${batsArgs[@]}" - ) - else - # shellcheck disable=SC2034 - local -a dockerRunCmd=( - "/bash/bin/test" - "${batsArgs[@]}" - ) - # shellcheck disable=SC2034 - local -a dockerRunArgs - if [[ "$0" =~ ^/ ]]; then - dockerRunArgs+=( - -v "$0:/bash/bin/test" - ) - else - dockerRunArgs+=( - -v "$(pwd -P)/$0:/bash/bin/test" - ) - fi - - # shellcheck disable=SC2154 - Docker::runBuildContainer \ - "${optionVendor}" \ - "${optionBashVersion}" \ - "${optionBashBaseImage}" \ - "${optionSkipDockerBuild}" \ - "${optionTraceVerbose}" \ - "${optionContinuousIntegrationMode}" \ - dockerRunCmd \ - dockerRunArgs - fi -} - -if [[ "${BASH_FRAMEWORK_QUIET_MODE:-0}" = "1" ]]; then - run &>/dev/null -else - run -fi diff --git a/test.sh b/test.sh index 588045e0..1bdf7bea 100755 --- a/test.sh +++ b/test.sh @@ -33,7 +33,13 @@ declare -a localDockerRunArgs=( -v "${CURRENT_DIR}:/bash" --entrypoint /usr/local/bin/bash ) -# shellcheck disable=SC2154 + +if [[ -d "${CURRENT_DIR}/vendor/bash-tools-framework" ]]; then + FRAMEWORK_ROOT_DIR="$(cd "${CURRENT_DIR}/vendor/bash-tools-framework" && pwd -P)" + localDockerRunArgs+=( + -v "${FRAMEWORK_ROOT_DIR}:/bash/vendor/bash-tools-framework" + ) +fi if [[ "${CI_MODE:-0}" = "0" ]]; then localDockerRunArgs+=(-v "/tmp:/tmp") localDockerRunArgs+=(-it)