Code change:
<ins> #!/usr/bin/env bash # Copyright 2018 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # #     http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. set -o errexit set -o nounset set -o pipefail KUBE_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. source "${KUBE_ROOT}/hack/lib/init.sh" # upstream shellcheck latest stable image as of January 10th, 2019 SHELLCHECK_IMAGE="koalaman/shellcheck-alpine:v0.6.0@sha256:7d4d712a2686da99d37580b4e2f45eb658b74e4b01caf67c1099adc294b96b52" SHELLCHECK_CONTAINER="k8s-shellcheck" # disabled lints disabled=( # this lint dissalows non-constant source, which we use extensively 1090 # this lint prefers command -v to which, they are not the same 2230 ) # comma separate for passing to shellcheck join_by() { local IFS="$1"; shift; echo "$*"; } SHELLCHECK_DISABLED="$(join_by , "${disabled[@]}")" readonly SHELLCHECK_DISABLED cd "${KUBE_ROOT}" # find all shell scripts excluding ./_*, ./.git/*, ./vendor*, # and anything git-ignored all_shell_scripts=() while IFS=$'n' read -r script; do git check-ignore -q "$script" || all_shell_scripts+=("$script"); done < <(find . -name "*.sh"  -not (  -path ./_*      -o  -path ./.git*   -o  -path ./vendor*     )) # make sure known failures are sorted failure_file="${KUBE_ROOT}/hack/.shellcheck_failures" if ! diff -u "${failure_file}" <(LC_ALL=C sort "${failure_file}"); then { echo echo "hack/.shellcheck_failures is not in alphabetical order. Please sort it:" echo echo "  LC_ALL=C sort -o hack/.shellcheck_failures hack/.shellcheck_failures" echo } >&2 false fi # load known failure files failing_files=() while IFS=$'n' read -r script; do failing_files+=("$script"); done < <(cat "${failure_file}") # TODO(bentheelder): we should probably move this and the copy in verify-golint.sh # to one of the bash libs array_contains () { local seeking=$1; shift # shift will iterate through the array local in=1 # in holds the exit status for the function for element; do if [[ "$element" == "$seeking" ]]; then in=0 # set in to 0 since we found it break fi done return $in } # creates the shellcheck container for later user create_container () { # TODO(bentheelder): this is a performance hack, we create the container with # a sleep MAX_INT32 so that it is effectively paused. # We then repeatedly exec to it to run each shellcheck, and later rm it when # we're done. # This is incredibly much faster than creating a container for each shellcheck # call ... docker run --name "${SHELLCHECK_CONTAINER}" -d --rm -v "${KUBE_ROOT}:${KUBE_ROOT}" -w "${KUBE_ROOT}" --entrypoint="sleep" "${SHELLCHECK_IMAGE}" 2147483647 } # removes the shellcheck container remove_container () { docker rm -f "${SHELLCHECK_CONTAINER}" &> /dev/null || true } # remove any previous container, ensure we will attempt to cleanup on exit, # and create the container remove_container trap remove_container EXIT if ! output="$(create_container 2>&1)"; then { echo "Failed to create shellcheck container with output: " echo "" echo "${output}" } >&2 exit 1 fi # lint each script, tracking failures errors=() not_failing=() for f in "${all_shell_scripts[@]}"; do set +o errexit failedLint=$(docker exec -t "${SHELLCHECK_CONTAINER}" shellcheck --exclude="${SHELLCHECK_DISABLED}" "${f}") set -o errexit array_contains "${f}" "${failing_files[@]}" && in_failing=$? || in_failing=$? if [[ -n "${failedLint}" ]] && [[ "${in_failing}" -ne "0" ]]; then errors+=( "${failedLint}" ) fi if [[ -z "${failedLint}" ]] && [[ "${in_failing}" -eq "0" ]]; then not_failing+=( "${f}" ) fi done # Check to be sure all the packages that should pass lint are. if [ ${#errors[@]} -eq 0 ]; then echo 'Congratulations!  All shell files have been linted.' else { echo "Errors from shellcheck:" for err in "${errors[@]}"; do echo "$err" done echo echo 'Please review the above warnings. You can test via "./hack/verify-shellcheck"' echo 'If the above warnings do not make sense, you can exempt this package from shellcheck' echo 'checking by adding it to hack/.shellcheck_failures (if your reviewer is okay with it).' echo } >&2 false fi if [[ ${#not_failing[@]} -gt 0 ]]; then { echo "Some packages in hack/.shellcheck_failures are passing shellcheck. Please remove them." echo for f in "${not_failing[@]}"; do echo "  $f" done echo } >&2 false fi # Check that all failing_packages actually still exist gone=() for f in "${failing_files[@]}"; do array_contains "$f" "${all_shell_scripts[@]}" || gone+=( "$f" ) done if [[ ${#gone[@]} -gt 0 ]]; then { echo "Some files in hack/.shellcheck_failures do not exist anymore. Please remove them." echo for f in "${gone[@]}"; do echo "  $f" done echo } >&2 false fi </ins>

Related discussion:
text: is a bash script linter which could help catch common problems in our (numerous) bash scripts. I know we'd like to reduce our use of bash scripts, but until then I think being stricter about script quality would be helpful. /cc
cc you were talking about this earlier. It has to only check modified lines.
False positives are a problem. For instance, not being able to follow a call through an environment variable causes an error. I would be much more comfortable with a non-blocking way to point out these issues, and leave to it human judgement.
Yeah, I've been interested in doing this for a while, but can be pretty persnickety and I haven't yet taken the time to configure it to be less noisy (or figure out if it can be). It's good, though.
Yeah, it would definitely need to be configured to ignore a lot of types of errors, but I still think it could be used to check for some types of errors. We could start with a pretty minimal set.
I would settle for something that just produced a quick-fix format artifact in the GCS bucket, and then the PR munger reading from it and commenting on the PR. We could re-use that for other linters too.
has been pretty accurate historically.
shellcheck, not spellcheck
reading too many issues in a on
/sig testing /priority awaiting-more-evidence Might be more noise than it's worth, but we do pylint our .py scripts now
Issues go stale after 90d of inactivity. Mark the issue as fresh with . Stale issues rot after an additional 30d of inactivity and eventually close. Prevent issues from auto-closing with an comment. If this issue is safe to close now please do so with . Send feedback to sig-testing, kubernetes/test-infra and/or . /lifecycle stale
Stale issues rot after 30d of inactivity. Mark the issue as fresh with . Rotten issues close after an additional 30d of inactivity. If this issue is safe to close now please do so with . Send feedback to sig-testing, kubernetes/test-infra and/or . /lifecycle rotten /remove-lifecycle stale
Rotten issues close after 30d of inactivity. Reopen the issue with . Mark the issue as fresh with .