diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml new file mode 100644 index 0000000..88e623d --- /dev/null +++ b/.github/workflows/shellcheck.yml @@ -0,0 +1,117 @@ +# This is a reusable workflow for running ShellCheck, +# a linter for shell scripts (https://shellcheck.net). + +# For more context, see: +# https://github.com/openedx/.github/blob/master/docs/decisions/0001-shellcheck.rst + +name: ShellCheck + +on: + workflow_call: + inputs: + + exclude-patterns: + type: string + required: false + default: "" + description: | + File paths matching these patterns will be skipped when running ShellCheck. + Space-separated. Patterns containing spaces are not supported. + Wildcard asterisks (*) are supported. + This pattern will be matched against paths which all begin with './'; so, + in order to match any paths, your patterns should each begin with './' or '*'. + + operating-system: + type: string + required: false + default: "ubuntu" + description: | + Operating system on which to run ShellCheck. Options are 'ubuntu' and 'macos'. + + shellcheck-version: + type: string + required: false + default: "v0.9.0" + description: + ShellCheck version to install. + Must be a tag or branch of https://github.com/koalaman/shellcheck. + By default, the upstream action will use a reasonable, stable shellcheck version, + which will be updated over time as new stable ShellCheck versions come out. + If you want your repositoriy's build to be totally deterministic, though, then + override with a specific version pin here, and manage updates ShellCheck yourself. + + shellcheck-options: + type: string + required: false + default: "" + description: | + Command-line options to forward to shellcheck. + For details of available options, run "shellcheck --help." + +jobs: + shellcheck: + + runs-on: "${{ inputs.operating-system }}-latest" + + defaults: + run: + # Specifying bash ensures that `-o pipefail` (exit when the input to a pipeline fails) is enabled. + shell: bash + + env: + SHELLCHECK_ARCHIVE: "" # We set this below based on operating-system. + + steps: + + - name: (Setup) Choose Linux AMD64 ShellCheck archive + if: inputs.operating-system == 'ubuntu' + run: echo "SHELLCHECK_ARCHIVE=shellcheck-${{ inputs.shellcheck-version }}.linux.x86_64.tar.xz" >> "$GITHUB_ENV" + + - name: (Setup) Choose Darwin (macOS) AMD64 ShellCheck archive + if: inputs.operating-system == 'macos' + run: echo "SHELLCHECK_ARCHIVE=shellcheck-${{ inputs.shellcheck-version }}.darwin.x86_64.tar.xz" >> "$GITHUB_ENV" + + - name: (Setup) Fail if we did not choose a ShellCheck archive + if: env.SHELLCHECK_ARCHIVE == '' + run: | + echo "::error::Error: Invalid input for operating-system: ${{ inputs.operating-system }}. Must be 'ubuntu' or 'macos'." && exit 1 + + - name: (Setup) Download & unpack ShellCheck + run: curl -L --fail --silent --show-error "https://github.com/koalaman/shellcheck/releases/download/${{ inputs.shellcheck-version }}/${{ env.SHELLCHECK_ARCHIVE }}" | tar --extract --xz + + - name: (Setup) Install ShellCheck + run: sudo cp "shellcheck-${{ inputs.shellcheck-version }}/shellcheck" /usr/local/bin + + - name: (Setup) Check out repository branch + uses: actions/checkout@v3 + + - name: Build the command for findings shell scripts + run: | + shellcheck_find_cmd="find . -name '*.sh'" + # Convert space-delimited exclude pattern into a proper array of exclusions, + # and then loop through that array in order to build the 'find' command. + read -r -a exclude_patterns <<<'${{ inputs.exclude-patterns }}' + for exclude_pattern in "${exclude_patterns[@]}" ; do + shellcheck_find_cmd="$shellcheck_find_cmd ! -wholename '$exclude_pattern'" + done + echo "SHELLCHECK_FIND_CMD=$shellcheck_find_cmd" >> "$GITHUB_ENV" + + - name: Print helpful information + run: | + shellcheck -V + echo + echo "The following shell scripts will be checked:" + ${{ env.SHELLCHECK_FIND_CMD }} + echo + echo "If ShellCheck passes, the next step will have no output." + echo "If ShellCheck fails, you should see a list of violations." + echo "Each violation type has an SCXXXX code which can be looked up at https://www.shellcheck.net/wiki/SCXXXX" + echo "We recommend that you try to resolve any violations." + echo "In the case where resolving the violation doesn't make sense, you can use directives (https://www.shellcheck.net/wiki/Directive) to ignore a single violation instance or an entire shell script." + echo + + # This step is intentionally a big one-line command so that + # devs can easily copy it and run it on their own machine. + - name: Run ShellCheck + run: ${{ env.SHELLCHECK_FIND_CMD }} -print0 | xargs -0 shellcheck ${{ inputs.shellcheck-options }} + diff --git a/workflow-templates/shellcheck.yml b/workflow-templates/shellcheck.yml new file mode 100644 index 0000000..453aa08 --- /dev/null +++ b/workflow-templates/shellcheck.yml @@ -0,0 +1,32 @@ +# Run ShellCheck on PRs and $default-branch + +# For more context, see: +# https://github.com/openedx/.github/blob/master/docs/decisions/0001-shellcheck.rst + +name: ShellCheck + +on: + pull_request: + push: + branches: + - $default-branch + +permissions: + contents: read + +jobs: + shellcheck: + strategy: + matrix: + os: ["ubuntu", "macos"] + uses: openedx/.github/.github/workflows/shellcheck.yml@master + with: + # For details on the meaning of each of these arguments, see: + # https://github.com/openedx/.github/blob/master/.github/workflows/shellcheck.yml + # We exclude `./node_modules/*` by default because we want people to easily be able to + # copy and run the command locally. Local copies of most of our services have a `./node_modules` + # directory that we want to ignore. + exclude-patterns: "./node_modules/*" + operating-system: "${{ matrix.os }}" + #shellcheck-version: "v0.9.0" + #shellcheck-options: ""