Repository: luizm/action-sh-checker Branch: master Commit: 883217215b11 Files: 7 Total size: 13.9 KB Directory structure: gitextract_prljnb1b/ ├── .github/ │ └── workflows/ │ └── ci.yml ├── Dockerfile ├── LICENSE ├── README.md ├── action.yml ├── entrypoint.sh └── upgrade.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/ci.yml ================================================ --- name: Build on: push: branches: - master pull_request: branches: - master workflow_dispatch: jobs: lint: name: lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: sh-checker uses: ./. # Use the local action, at the current version with: sh_checker_comment: true sh_checker_only_diff: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: Dockerfile ================================================ FROM alpine:3.18.4 LABEL "name"="sh-checker" LABEL "maintainer"="Luiz Muller " ARG shfmt_version=3.12.0 ARG shellcheck_version=0.11.0 ARG gh_version=2.37.0 RUN apk add --no-cache bash git jq curl checkbashisms xz \ && apk add --no-cache --virtual .build-deps tar \ && wget "https://github.com/mvdan/sh/releases/download/v${shfmt_version}/shfmt_v${shfmt_version}_linux_amd64" -O /usr/local/bin/shfmt \ && chmod +x /usr/local/bin/shfmt \ && wget "https://github.com/koalaman/shellcheck/releases/download/v${shellcheck_version}/shellcheck-v${shellcheck_version}.linux.x86_64.tar.xz" -O- | tar xJ -C /usr/local/bin/ --strip-components=1 --wildcards '*/shellcheck' \ && chmod +x /usr/local/bin/shellcheck \ && curl -L https://github.com/cli/cli/releases/download/v${gh_version}/gh_${gh_version}_linux_amd64.tar.gz | tar xz -C /usr/local/ --strip-components=1 \ && apk del --no-cache .build-deps \ && rm -rf /tmp/* # https://github.com/actions/runner-images/issues/6775#issuecomment-1410270956 RUN git config --system --add safe.directory /github/workspace COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh ENTRYPOINT ["/entrypoint.sh"] ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020 Luiz Muller Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # sh-checker [![Build](https://github.com/luizm/action-sh-checker/actions/workflows/ci.yml/badge.svg)](https://github.com/luizm/action-sh-checker/actions/workflows/ci.yml) A [GitHub action](https://docs.github.com/en/free-pro-team@latest/actions) that performs static analysis of shell scripts using [shellcheck](https://github.com/koalaman/shellcheck), [shfmt](https://github.com/mvdan/sh) and [checkbashisms](https://linux.die.net/man/1/checkbashisms) ![Screen Shot 2020-04-01 at 12 18 59](https://user-images.githubusercontent.com/6004689/78155536-f9a8a080-7413-11ea-8b5c-2c96484feb61.png) ## Usage Job example to check all sh files but ignore the directory `.terraform` and file `dir/example.sh` ```yaml name: example on: - pull_request jobs: sh-checker: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Run the sh-checker uses: luizm/action-sh-checker@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SHELLCHECK_OPTS: -e SC1004 # exclude some shellcheck warnings. SHFMT_OPTS: -s # arguments to shfmt. with: sh_checker_comment: true sh_checker_exclude: ".terraform ^dir/example.sh" ``` ### Environment Variables `SHELLCHECK_OPTS`: Used to specify shellcheck arguments. `SHFMT_OPTS`: Used to specify shfmt arguments. ### Inputs `sh_checker_only_diff`: (optional) Only check the files that were changed in the pull request. Default is to check all files in the repo. `sh_checker_exclude`: (optional) Directory or file name that doesn't need to be checked. `sh_checker_comment`: (optional) If true, it will show the errors as commentaries in the pull requests. Default is false. `sh_checker_shfmt_disable`: (optional) If true, it will skip shfmt. Default is false. `sh_checker_shellcheck_disable`: (optional) If true, it will skip shellcheck. Default is false. `sh_checker_checkbashisms_enable`: (optional) If true, run checkbashisms tool against scripts. Default is false. ### Secrets `GITHUB_TOKEN`: The GitHub API token used to post comments to pull requests. Required only if `sh_checker_only_diff` or `sh_checker_comment` is set to true. ================================================ FILE: action.yml ================================================ name: "sh-checker" description: "Run shellcheck and/or shfmt on all shell script files and include commentaries, there is a exclude parameter" author: "luizm" branding: icon: "terminal" color: "black" inputs: sh_checker_exclude: description: "Directory or file name that doesn't need to be checked" required: false sh_checker_shfmt_disable: description: "If true, it will skip the shfmt. Default is false" required: false sh_checker_shellcheck_disable: description: "If true, it will skip the shellcheck. Default is false" required: false sh_checker_checkbashisms_enable: description: "If true, run checkbashisms tool against scripts. Default is false" required: false default: "false" sh_checker_comment: description: "If true, it will show the errors as commentaries in the pull requests. Default is false" required: false sh_checker_only_diff: description: "If true, run only on files changed in the PR branch. Default is false" required: false runs: using: "docker" image: "Dockerfile" ================================================ FILE: entrypoint.sh ================================================ #!/usr/bin/env bash cd "$GITHUB_WORKSPACE" || { printf 'Directory not found: "%s"\n' "$GITHUB_WORKSPACE" exit 1 } SHELLCHECK_DISABLE=0 SHFMT_DISABLE=0 SH_CHECKER_COMMENT=0 CHECKBASHISMS_ENABLE=0 SH_CHECKER_ONLY_DIFF=0 shopt -s nocasematch if [[ "${INPUT_SH_CHECKER_SHELLCHECK_DISABLE}" =~ ^(1|true|on|yes)$ ]]; then SHELLCHECK_DISABLE=1 fi if [[ "${INPUT_SH_CHECKER_SHFMT_DISABLE}" =~ ^(1|true|on|yes)$ ]]; then SHFMT_DISABLE=1 fi if [[ "${INPUT_SH_CHECKER_COMMENT}" =~ ^(1|true|on|yes)$ ]]; then SH_CHECKER_COMMENT=1 fi if [[ "${INPUT_SH_CHECKER_CHECKBASHISMS_ENABLE}" =~ ^(1|true|on|yes)$ ]]; then CHECKBASHISMS_ENABLE=1 fi if [[ "${INPUT_SH_CHECKER_ONLY_DIFF}" =~ ^(1|true|on|yes)$ ]]; then SH_CHECKER_ONLY_DIFF=1 fi if ((SHELLCHECK_DISABLE == 1 && SHFMT_DISABLE == 1 && CHECKBASHISMS_ENABLE != 1)); then echo "All checks are disabled: \`sh_checker_shellcheck_disable\` and \`sh_checker_shfmt_disable\` are both set to 1/true." fi # Internal functions _show_sh_files() { # Store the array of files to check in sh_files # using a global, as returning arrays in bash is ugly # setting IFS to \n allows for spaces in file names: if ((SH_CHECKER_ONLY_DIFF == 1)); then # Compute the intersection of all shell scripts in the repo and files changes on the PR branch if [[ "$GITHUB_REF" =~ ^refs/pull/ ]]; then # The `on: pull_request` trigger does not give branch information, so we need to supply the PR number to gh # See https://frontside.com/blog/2020-05-26-github-actions-pull_request/ # and https://docs.github.com/en/actions/learn-github-actions/variables pr_number="$(echo "$GITHUB_REF" | cut -d/ -f3)" else pr_number="" # have gh figure out the PR number from the branch name fi IFS=$'\n' mapfile -t sh_files < <(sort <(shfmt -f .) <(gh pr diff "$pr_number" --name-only) | uniq -d) echo "Checking only the shell script(s) changed in the PR branch:" printf '"%s"\n' "${sh_files[@]}" else IFS=$'\n' mapfile -t sh_files < <(shfmt -f .) fi if [ -z "$INPUT_SH_CHECKER_EXCLUDE" ]; then return 0 fi OLDIFS="$IFS" IFS=$' \t\n' read -d '' -ra excludes <<<"$INPUT_SH_CHECKER_EXCLUDE" IFS=$'\n' sh_all=("${sh_files[@]}") sh_files=() excluded=() local sh exclude for sh in "${sh_all[@]}"; do for exclude in "${excludes[@]}"; do grep -q -E "$exclude" <<<"$sh" || continue excluded+=("$sh") continue 2 done sh_files+=("$sh") done if (("${#excluded[@]}" != 0)); then printf 'The following %d shell script(s) will not be checked:\n' "${#excluded[@]}" printf '"%s"\n' "${excluded[@]}" fi IFS="$OLDIFS" } _comment_on_github() { local content IFS= read -r -d '' content < shellcheck errors \`\`\` $1 \`\`\`
shfmt errors \`\`\` $2 \`\`\`
EOF local -r payload=$(jq -R --slurp '{body: .}' <<<"$content") local -r comment_url=$(jq -r .pull_request.comments_url <"$GITHUB_EVENT_PATH") echo "Commenting on the pull request" curl -s -S -H "Authorization: token $GITHUB_TOKEN" --header "Content-Type: application/json" --data @- "$comment_url" <<<"$payload" } _show_sh_files ((${#sh_files[@]} == 0)) && { if ((SH_CHECKER_ONLY_DIFF == 1)); then echo "No shell scripts were changed." exit 0 fi if [ -n "$INPUT_SH_CHECKER_EXCLUDE" ]; then if ((${#excluded[@]} == ${#sh_all[@]})); then printf 'All %d shell script(s) have been excluded per your sh_checker_exclude setting:\n' "${#sh_all[@]}" IFS=$' \t\n' printf '"%s"\n' "${excludes[@]}" exit 0 fi fi echo "No shell scripts were found in this repository. Please check your settings." exit 0 } # Validate sh files shellcheck_code=0 checkbashisms_code=0 shfmt_code=0 exit_code=0 shellcheck_error='shellcheck checking is disabled.' shfmt_error='shfmt checking is disabled.' if ((SHELLCHECK_DISABLE != 1)); then printf "Validating %d shell script(s) using 'shellcheck %s':\\n" "${#sh_files[@]}" "$SHELLCHECK_OPTS" IFS=$' \t\n' read -d '' -ra args <<<"$SHELLCHECK_OPTS" shellcheck_output="$(shellcheck "${args[@]}" "${sh_files[@]}" 2>&1)" shellcheck_code=$? if ((shellcheck_code == 0)); then printf -v shellcheck_error "'shellcheck %s' found no issues.\\n" "$SHELLCHECK_OPTS" else # .shellcheck returns 0-4: https://github.com/koalaman/shellcheck/blob/dff8f9492a153b4ad8ac7d085136ce532e8ea081/shellcheck.hs#L191 exit_code=$shellcheck_code IFS= read -r -d '' shellcheck_error <&1)" shfmt_code=$? if ((shfmt_code == 0)); then printf -v shfmt_error "'shfmt %s' found no issues.\\n" "$SHFMT_OPTS" else # shfmt returns 0 or 1: https://github.com/mvdan/sh/blob/dbbad59b44d586c0f3d044a3820c18c41b495e2a/cmd/shfmt/main.go#L72 ((exit_code |= 8)) IFS= read -r -d '' shfmt_error </dev/null; then # -sq == --silent --disable dl="curl -sq" else # -q0 == --quiet --output-document dl='wget --no-config -qO -' fi ALPINE_VER=$(${dl} 'https://registry.hub.docker.com/v2/repositories/library/alpine/tags/' | jq -r '.results[1].name') GH_VER=$(${dl} https://api.github.com/repos/cli/cli/tags | jq -r '.[0].name') SHELLCHECK_VER=$(${dl} https://api.github.com/repos/koalaman/shellcheck/tags | jq -r '.[0].name') SHFMT_VER=$(${dl} https://api.github.com/repos/mvdan/sh/tags | jq -r '.[0].name') GH_VER="${GH_VER/v/}" SHELLCHECK_VER="${SHELLCHECK_VER/v/}" SHFMT_VER="${SHFMT_VER/v/}" sed -Ei " s/^(FROM\s+alpine:).*/\1${ALPINE_VER}/; s/^(ARG\s+gh_version=).*/\1${GH_VER}/; s/^(ARG\s+shellcheck_version=).*/\1${SHELLCHECK_VER}/; s/^(ARG\s+shfmt_version=).*/\1${SHFMT_VER}/; " Dockerfile git diff