Skip to content

Commit 2069a0a

Browse files
authored
Merge pull request kubernetes-csi#11 from pohly/verify-shellcheck
verify shellcheck
2 parents 3b6af7b + 6c7ba1b commit 2069a0a

File tree

4 files changed

+321
-0
lines changed

4 files changed

+321
-0
lines changed

README.md

+12
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,15 @@ Cheat sheet:
4949
- `git subtree add --prefix=release-tools https://github.com/kubernetes-csi/csi-release-tools.git master` - add release tools to a repo which does not have them yet (only once)
5050
- `git subtree pull --prefix=release-tools https://github.com/kubernetes-csi/csi-release-tools.git master` - update local copy to latest upstream (whenever upstream changes)
5151
- edit, `git commit`, `git subtree push --prefix=release-tools [email protected]:<user>/csi-release-tools.git <my-new-or-existing-branch>` - push to a new branch before submitting a PR
52+
53+
verify-shellcheck.sh
54+
--------------------
55+
56+
The [verify-shellcheck.sh](./verify-shellcheck.sh) script in this repo
57+
is a stripped down copy of the [corresponding
58+
script](https://github.com/kubernetes/kubernetes/blob/release-1.14/hack/verify-shellcheck.sh)
59+
in the Kubernetes repository. It can be used to check for certain
60+
errors shell scripts, like missing quotation marks. The default
61+
`test-shellcheck` target in [build.make](./build.make) only checks the
62+
scripts in this directory. Components can add more directories to
63+
`TEST_SHELLCHECK_DIRS` to check also other scripts.

build.make

+15
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,18 @@ test: test-subtree
132132
test-subtree:
133133
@ echo; echo "### $@:"
134134
./release-tools/verify-subtree.sh release-tools
135+
136+
# Components can extend the set of directories which must pass shellcheck.
137+
# The default is to check only the release-tools directory itself.
138+
TEST_SHELLCHECK_DIRS=release-tools
139+
.PHONY: test-shellcheck
140+
test: test-shellcheck
141+
test-shellcheck:
142+
@ echo; echo "### $@:"
143+
@ ret=0; \
144+
for dir in $(abspath $(TEST_SHELLCHECK_DIRS)); do \
145+
echo; \
146+
echo "$$dir:"; \
147+
./release-tools/verify-shellcheck.sh "$$dir" || ret=1; \
148+
done; \
149+
return $$ret

util.sh

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
#!/usr/bin/env bash
2+
3+
# Copyright 2014 The Kubernetes Authors.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
function kube::util::sourced_variable {
18+
# Call this function to tell shellcheck that a variable is supposed to
19+
# be used from other calling context. This helps quiet an "unused
20+
# variable" warning from shellcheck and also document your code.
21+
true
22+
}
23+
24+
kube::util::sortable_date() {
25+
date "+%Y%m%d-%H%M%S"
26+
}
27+
28+
# arguments: target, item1, item2, item3, ...
29+
# returns 0 if target is in the given items, 1 otherwise.
30+
kube::util::array_contains() {
31+
local search="$1"
32+
local element
33+
shift
34+
for element; do
35+
if [[ "${element}" == "${search}" ]]; then
36+
return 0
37+
fi
38+
done
39+
return 1
40+
}
41+
42+
# Example: kube::util::trap_add 'echo "in trap DEBUG"' DEBUG
43+
# See: http://stackoverflow.com/questions/3338030/multiple-bash-traps-for-the-same-signal
44+
kube::util::trap_add() {
45+
local trap_add_cmd
46+
trap_add_cmd=$1
47+
shift
48+
49+
for trap_add_name in "$@"; do
50+
local existing_cmd
51+
local new_cmd
52+
53+
# Grab the currently defined trap commands for this trap
54+
existing_cmd=$(trap -p "${trap_add_name}" | awk -F"'" '{print $2}')
55+
56+
if [[ -z "${existing_cmd}" ]]; then
57+
new_cmd="${trap_add_cmd}"
58+
else
59+
new_cmd="${trap_add_cmd};${existing_cmd}"
60+
fi
61+
62+
# Assign the test. Disable the shellcheck warning telling that trap
63+
# commands should be single quoted to avoid evaluating them at this
64+
# point instead evaluating them at run time. The logic of adding new
65+
# commands to a single trap requires them to be evaluated right away.
66+
# shellcheck disable=SC2064
67+
trap "${new_cmd}" "${trap_add_name}"
68+
done
69+
}
70+
71+
kube::util::download_file() {
72+
local -r url=$1
73+
local -r destination_file=$2
74+
75+
rm "${destination_file}" 2&> /dev/null || true
76+
77+
for i in $(seq 5)
78+
do
79+
if ! curl -fsSL --retry 3 --keepalive-time 2 "${url}" -o "${destination_file}"; then
80+
echo "Downloading ${url} failed. $((5-i)) retries left."
81+
sleep 1
82+
else
83+
echo "Downloading ${url} succeed"
84+
return 0
85+
fi
86+
done
87+
return 1
88+
}
89+
90+
# Wait for background jobs to finish. Return with
91+
# an error status if any of the jobs failed.
92+
kube::util::wait-for-jobs() {
93+
local fail=0
94+
local job
95+
for job in $(jobs -p); do
96+
wait "${job}" || fail=$((fail + 1))
97+
done
98+
return ${fail}
99+
}
100+
101+
# kube::util::join <delim> <list...>
102+
# Concatenates the list elements with the delimiter passed as first parameter
103+
#
104+
# Ex: kube::util::join , a b c
105+
# -> a,b,c
106+
function kube::util::join {
107+
local IFS="$1"
108+
shift
109+
echo "$*"
110+
}
111+
112+
# kube::util::check-file-in-alphabetical-order <file>
113+
# Check that the file is in alphabetical order
114+
#
115+
function kube::util::check-file-in-alphabetical-order {
116+
local failure_file="$1"
117+
if ! diff -u "${failure_file}" <(LC_ALL=C sort "${failure_file}"); then
118+
{
119+
echo
120+
echo "${failure_file} is not in alphabetical order. Please sort it:"
121+
echo
122+
echo " LC_ALL=C sort -o ${failure_file} ${failure_file}"
123+
echo
124+
} >&2
125+
false
126+
fi
127+
}
128+
129+
# Some useful colors.
130+
if [[ -z "${color_start-}" ]]; then
131+
declare -r color_start="\033["
132+
declare -r color_red="${color_start}0;31m"
133+
declare -r color_yellow="${color_start}0;33m"
134+
declare -r color_green="${color_start}0;32m"
135+
declare -r color_blue="${color_start}1;34m"
136+
declare -r color_cyan="${color_start}1;36m"
137+
declare -r color_norm="${color_start}0m"
138+
139+
kube::util::sourced_variable "${color_start}"
140+
kube::util::sourced_variable "${color_red}"
141+
kube::util::sourced_variable "${color_yellow}"
142+
kube::util::sourced_variable "${color_green}"
143+
kube::util::sourced_variable "${color_blue}"
144+
kube::util::sourced_variable "${color_cyan}"
145+
kube::util::sourced_variable "${color_norm}"
146+
fi
147+
148+
# ex: ts=2 sw=2 et filetype=sh

verify-shellcheck.sh

+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
#!/usr/bin/env bash
2+
3+
# Copyright 2018 The Kubernetes Authors.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
set -o errexit
18+
set -o nounset
19+
set -o pipefail
20+
21+
# The csi-release-tools directory.
22+
TOOLS="$(dirname "${BASH_SOURCE[0]}")"
23+
. "${TOOLS}/util.sh"
24+
25+
# Directory to check. Default is the parent of the tools themselves.
26+
ROOT="${1:-${TOOLS}/..}"
27+
28+
# required version for this script, if not installed on the host we will
29+
# use the official docker image instead. keep this in sync with SHELLCHECK_IMAGE
30+
SHELLCHECK_VERSION="0.6.0"
31+
# upstream shellcheck latest stable image as of January 10th, 2019
32+
SHELLCHECK_IMAGE="koalaman/shellcheck-alpine:v0.6.0@sha256:7d4d712a2686da99d37580b4e2f45eb658b74e4b01caf67c1099adc294b96b52"
33+
34+
# fixed name for the shellcheck docker container so we can reliably clean it up
35+
SHELLCHECK_CONTAINER="k8s-shellcheck"
36+
37+
# disabled lints
38+
disabled=(
39+
# this lint disallows non-constant source, which we use extensively without
40+
# any known bugs
41+
1090
42+
# this lint prefers command -v to which, they are not the same
43+
2230
44+
)
45+
# comma separate for passing to shellcheck
46+
join_by() {
47+
local IFS="$1";
48+
shift;
49+
echo "$*";
50+
}
51+
SHELLCHECK_DISABLED="$(join_by , "${disabled[@]}")"
52+
readonly SHELLCHECK_DISABLED
53+
54+
# creates the shellcheck container for later use
55+
create_container () {
56+
# TODO(bentheelder): this is a performance hack, we create the container with
57+
# a sleep MAX_INT32 so that it is effectively paused.
58+
# We then repeatedly exec to it to run each shellcheck, and later rm it when
59+
# we're done.
60+
# This is incredibly much faster than creating a container for each shellcheck
61+
# call ...
62+
docker run --name "${SHELLCHECK_CONTAINER}" -d --rm -v "${ROOT}:${ROOT}" -w "${ROOT}" --entrypoint="sleep" "${SHELLCHECK_IMAGE}" 2147483647
63+
}
64+
# removes the shellcheck container
65+
remove_container () {
66+
docker rm -f "${SHELLCHECK_CONTAINER}" &> /dev/null || true
67+
}
68+
69+
# ensure we're linting the source tree
70+
cd "${ROOT}"
71+
72+
# find all shell scripts excluding ./_*, ./.git/*, ./vendor*,
73+
# and anything git-ignored
74+
all_shell_scripts=()
75+
while IFS=$'\n' read -r script;
76+
do git check-ignore -q "$script" || all_shell_scripts+=("$script");
77+
done < <(find . -name "*.sh" \
78+
-not \( \
79+
-path ./_\* -o \
80+
-path ./.git\* -o \
81+
-path ./vendor\* \
82+
\))
83+
84+
# detect if the host machine has the required shellcheck version installed
85+
# if so, we will use that instead.
86+
HAVE_SHELLCHECK=false
87+
if which shellcheck &>/dev/null; then
88+
detected_version="$(shellcheck --version | grep 'version: .*')"
89+
if [[ "${detected_version}" = "version: ${SHELLCHECK_VERSION}" ]]; then
90+
HAVE_SHELLCHECK=true
91+
fi
92+
fi
93+
94+
# tell the user which we've selected and possibly set up the container
95+
if ${HAVE_SHELLCHECK}; then
96+
echo "Using host shellcheck ${SHELLCHECK_VERSION} binary."
97+
else
98+
echo "Using shellcheck ${SHELLCHECK_VERSION} docker image."
99+
# remove any previous container, ensure we will attempt to cleanup on exit,
100+
# and create the container
101+
remove_container
102+
kube::util::trap_add 'remove_container' EXIT
103+
if ! output="$(create_container 2>&1)"; then
104+
{
105+
echo "Failed to create shellcheck container with output: "
106+
echo ""
107+
echo "${output}"
108+
} >&2
109+
exit 1
110+
fi
111+
fi
112+
113+
# lint each script, tracking failures
114+
errors=()
115+
for f in "${all_shell_scripts[@]}"; do
116+
set +o errexit
117+
if ${HAVE_SHELLCHECK}; then
118+
failedLint=$(shellcheck --exclude="${SHELLCHECK_DISABLED}" "${f}")
119+
else
120+
failedLint=$(docker exec -t ${SHELLCHECK_CONTAINER} \
121+
shellcheck --exclude="${SHELLCHECK_DISABLED}" "${f}")
122+
fi
123+
set -o errexit
124+
if [[ -n "${failedLint}" ]]; then
125+
errors+=( "${failedLint}" )
126+
fi
127+
done
128+
129+
# Check to be sure all the packages that should pass lint are.
130+
if [ ${#errors[@]} -eq 0 ]; then
131+
echo 'Congratulations! All shell files are passing lint.'
132+
else
133+
{
134+
echo "Errors from shellcheck:"
135+
for err in "${errors[@]}"; do
136+
echo "$err"
137+
done
138+
echo
139+
echo 'Please review the above warnings. You can test via "./hack/verify-shellcheck"'
140+
echo 'If the above warnings do not make sense, you can exempt them from shellcheck'
141+
echo 'checking by adding the "shellcheck disable" directive'
142+
echo '(https://github.com/koalaman/shellcheck/wiki/Directive#disable).'
143+
echo
144+
} >&2
145+
false
146+
fi

0 commit comments

Comments
 (0)