diff --git a/demos/claude-code-multiplex/workload/run.sh b/demos/claude-code-multiplex/workload/run.sh index b135086..44d2d25 100755 --- a/demos/claude-code-multiplex/workload/run.sh +++ b/demos/claude-code-multiplex/workload/run.sh @@ -1,4 +1,5 @@ -#!/bin/bash +#!/usr/bin/env bash + # Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/hack/create-kind-cluster.sh b/hack/create-kind-cluster.sh index dbe458d..2915a95 100755 --- a/hack/create-kind-cluster.sh +++ b/hack/create-kind-cluster.sh @@ -1,4 +1,5 @@ -#!/bin/bash +#!/usr/bin/env bash + # Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/hack/delete-kind-cluster.sh b/hack/delete-kind-cluster.sh index 4cf5ce5..84374ee 100755 --- a/hack/delete-kind-cluster.sh +++ b/hack/delete-kind-cluster.sh @@ -1,4 +1,5 @@ -#!/bin/bash +#!/usr/bin/env bash + # Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/hack/install-ate-kind.sh b/hack/install-ate-kind.sh index 392f676..74752c8 100755 --- a/hack/install-ate-kind.sh +++ b/hack/install-ate-kind.sh @@ -1,4 +1,5 @@ -#!/bin/bash +#!/usr/bin/env bash + # Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,16 +20,19 @@ set -o pipefail ROOT=$(git rev-parse --show-toplevel) +# shellcheck disable=SC2155 # safe initialization +goarch=$(go env GOARCH) + # override reading dev env -export NO_DEV_ENV=true +export NO_DEV_ENV="true" # we will push images to the local registry -export KO_DOCKER_REPO=${KO_DOCKER_REPO:-localhost:5001} +export KO_DOCKER_REPO="${KO_DOCKER_REPO:-localhost:5001}" # we want to build for the host architecture -export KO_DEFAULTPLATFORMS=linux/$(go env GOARCH) +export KO_DEFAULTPLATFORMS="linux/${goarch}" # install resolved manifests using Kustomize overlay for local Kind cluster -export ATE_INSTALL_KIND=true +export ATE_INSTALL_KIND="true" # use default bucket name for local deployment -export BUCKET_NAME=ate-snapshots +export BUCKET_NAME="ate-snapshots" # unset other env from ate-dev-env.sh in case the developer already sourced them unset GCE_REGION CLUSTER_LOCATION NETWORK SUBNETWORK MEMORYSTORE_INSTANCE PROJECT_ID diff --git a/hack/install-ate.sh b/hack/install-ate.sh index 8e043e8..86805f5 100755 --- a/hack/install-ate.sh +++ b/hack/install-ate.sh @@ -1,4 +1,5 @@ -#!/bin/bash +#!/usr/bin/env bash + # Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -121,7 +122,7 @@ create_valkey_ca_certs_secret() { ca_certs=$(echo "${der_base64}" | base64 --decode | openssl x509 -inform der -outform pem) run_kubectl create secret generic valkey-ca-certs \ - --from-literal=ca.crt="$(echo "${ca_certs}")" \ + --from-literal=ca.crt="${ca_certs}" \ -n ate-system \ --dry-run=client -o yaml \ | run_kubectl apply -f - diff --git a/hack/install-demo-agent-secret.sh b/hack/install-demo-agent-secret.sh index 64595da..659708c 100644 --- a/hack/install-demo-agent-secret.sh +++ b/hack/install-demo-agent-secret.sh @@ -1,3 +1,5 @@ +#!/usr/bin/env bash + # Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/hack/install-demo-claude-code-multiplex.sh b/hack/install-demo-claude-code-multiplex.sh index 0e68ecb..c1e855a 100644 --- a/hack/install-demo-claude-code-multiplex.sh +++ b/hack/install-demo-claude-code-multiplex.sh @@ -1,3 +1,5 @@ +#!/usr/bin/env bash + # Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,6 +35,7 @@ demo-claude-code-multiplex_cmdline() { # binary), so it uses docker buildx rather than ko. demo-claude-code-multiplex_build_workload() { local repo="${KO_DOCKER_REPO}/claude-multiplex-demo-workload" + # shellcheck disable=SC2155 # safe initialization local stage_tag="${repo}:build-$(date +%s)" docker buildx build \ --platform=linux/amd64 \ diff --git a/hack/install-demo-counter.sh b/hack/install-demo-counter.sh index d436c3a..501f77a 100644 --- a/hack/install-demo-counter.sh +++ b/hack/install-demo-counter.sh @@ -1,3 +1,5 @@ +#!/usr/bin/env bash + # Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/hack/install-demo-sandbox.sh b/hack/install-demo-sandbox.sh index 2126e4a..5c87988 100644 --- a/hack/install-demo-sandbox.sh +++ b/hack/install-demo-sandbox.sh @@ -1,3 +1,5 @@ +#!/usr/bin/env bash + # Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/hack/run-e2e.sh b/hack/run-e2e.sh index cf33654..689b841 100755 --- a/hack/run-e2e.sh +++ b/hack/run-e2e.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Copyright 2026 Google LLC # diff --git a/hack/teardown.sh b/hack/teardown.sh index 3ebddc6..7088155 100755 --- a/hack/teardown.sh +++ b/hack/teardown.sh @@ -1,4 +1,5 @@ -#!/bin/bash +#!/usr/bin/env bash + # Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/hack/third_party/kubernetes/verify-shellcheck.sh b/hack/third_party/kubernetes/verify-shellcheck.sh new file mode 100755 index 0000000..60200fe --- /dev/null +++ b/hack/third_party/kubernetes/verify-shellcheck.sh @@ -0,0 +1,129 @@ +#!/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. + +# This script lints each shell script by `shellcheck`. +# Usage: `hack/verify-shellcheck.sh`. + +set -o errexit +set -o nounset +set -o pipefail + +ROOT="$(git rev-parse --show-toplevel)" +cd "${ROOT}" + +# allow overriding docker cli, which should work fine for this script +DOCKER="${DOCKER:-docker}" + +# required version for this script, if not installed on the host we will +# use the official docker image instead. keep this in sync with SHELLCHECK_IMAGE +SHELLCHECK_VERSION="0.9.0" +SHELLCHECK_IMAGE="docker.io/koalaman/shellcheck:v0.9.0@sha256:f35e8987b02760d4e76fc99a68ad5c42cc10bb32f3dd2143a3cf92f1e5446a45" + +# disabled lints +disabled=( + # this lint warns when shellcheck cannot find a sourced file + # this wouldn't be a bad idea to warn on, but it fails on lots of path + # dependent sourcing, so just disable enforcing it + 1091 +) + +# comma separate for passing to shellcheck +join_by() { + local IFS="$1"; + shift; + echo "$*"; +} +SHELLCHECK_DISABLED="$(join_by , "${disabled[@]}")" +readonly SHELLCHECK_DISABLED + +scripts_to_check=("$@") +if [[ "$#" == 0 ]]; then + # Find all shell scripts excluding: + # - Anything git-ignored - No need to lint untracked files. + # - ./_* - No need to lint output directories. + # - ./.git/* - Ignore anything in the git object store. + # - */third_party/* - cope we copied from elsewhere. + while IFS=$'\n' read -r script; + do git check-ignore -q "$script" || scripts_to_check+=("$script"); + done < <(find . -name '*.sh' \ + -not \( \ + -path './_*' -o \ + -path './.git*' -o \ + \( -path '*/third_party/*' \) \ + \)) +fi + +# detect if the host machine has the required shellcheck version installed +# if so, we will use that instead. +HAVE_SHELLCHECK=false +if which shellcheck &>/dev/null; then + detected_version="$(shellcheck --version | grep 'version: .*')" + if [[ "${detected_version}" = "version: ${SHELLCHECK_VERSION}" ]]; then + HAVE_SHELLCHECK=true + fi +fi + +# Set this to "always" to force color output. +# Set this to "never" to disable color output (which can confuse parsers like +# junit). +SHELLCHECK_COLOR="${SHELLCHECK_COLOR:-auto}" + +# common arguments we'll pass to shellcheck +SHELLCHECK_OPTIONS=( + # allow following sourced files that are not specified in the command, + # we need this because we specify one file at a time in order to trivially + # detect which files are failing + "--external-sources" + # include our disabled lints + "--exclude=${SHELLCHECK_DISABLED}" + # set colorized output + "--color=${SHELLCHECK_COLOR}" +) + +# tell the user which we've selected and lint all scripts +# The shellcheck errors are printed to stdout by default, hence they need to be redirected +# to stderr in order to be well parsed for Junit representation by juLog function +res=0 +if ${HAVE_SHELLCHECK}; then + if [ "${VERBOSE:-}" == "true" ]; then + echo "Using host shellcheck ${SHELLCHECK_VERSION} binary." + fi + shellcheck "${SHELLCHECK_OPTIONS[@]}" "${scripts_to_check[@]}" >&2 || res=$? +else + if [ "${VERBOSE:-}" == "true" ]; then + echo "Using shellcheck ${SHELLCHECK_VERSION} docker image." + fi + "${DOCKER}" run \ + --rm -v "${ROOT}:${ROOT}" -w "${ROOT}" --security-opt label=disable \ + "${SHELLCHECK_IMAGE}" \ + "${SHELLCHECK_OPTIONS[@]}" "${scripts_to_check[@]}" >&2 || res=$? +fi + +# print a message based on the result +if [ $res -ne 0 ]; then + { + echo + echo 'If the above warnings do not make sense, you can exempt this warning with a comment' + echo ' (if your reviewer is okay with it).' + echo 'In general please prefer to fix the error, we have already disabled specific lints' + echo ' that the project chooses to ignore.' + echo 'See: https://github.com/koalaman/shellcheck/wiki/Ignore#ignoring-one-specific-instance-in-a-file' + echo + } >&2 +fi + +# preserve the result +exit $res diff --git a/hack/update-all.sh b/hack/update-all.sh index 99d6bc4..af341ec 100755 --- a/hack/update-all.sh +++ b/hack/update-all.sh @@ -19,8 +19,9 @@ set -o nounset set -o pipefail ROOT="$(git rev-parse --show-toplevel)" - cd "${ROOT}" + +# shellcheck disable=2044 # for-loop over find output is intentional for F in $(find ./hack/update -name '*.sh'); do echo "Running ${F}" "${F}" "$@" diff --git a/hack/update/gofmt.sh b/hack/update/gofmt.sh index dca2220..8c62f4a 100755 --- a/hack/update/gofmt.sh +++ b/hack/update/gofmt.sh @@ -22,6 +22,7 @@ ROOT="$(git rev-parse --show-toplevel)" cd "${ROOT}" # Find all top-level directories containing Go files, and run gofmt on them. +# shellcheck disable=SC2207 # read array dirs=( $(git ls-files -cmo --exclude-standard ':(glob)**/*.go' \ `# Omit LICENSES/, which can contain bundled source code. ` \ @@ -30,6 +31,6 @@ dirs=( | uniq) ) -for dir in ${dirs[@]}; do - gofmt -s -w ${dir} +for dir in "${dirs[@]}"; do + gofmt -s -w "${dir}" done diff --git a/hack/update/licenses.sh b/hack/update/licenses.sh index a1007b8..c706933 100755 --- a/hack/update/licenses.sh +++ b/hack/update/licenses.sh @@ -38,7 +38,9 @@ targets=( "darwin/arm64" ) +tmpfile="" # make shellcheck happy tmpfile="$(mktemp -t "update-licenses.XXXXXX")" +# shellcheck disable=SC2064 # evaluate $tmpfile immediately trap "rm -f ${tmpfile}" EXIT for target in "${targets[@]}"; do diff --git a/hack/verify-all.sh b/hack/verify-all.sh index 64dcb62..776f28f 100755 --- a/hack/verify-all.sh +++ b/hack/verify-all.sh @@ -19,8 +19,9 @@ set -o nounset set -o pipefail ROOT="$(git rev-parse --show-toplevel)" - cd "${ROOT}" + +# shellcheck disable=2044 # for-loop over find output is intentional for F in $(find ./hack/verify -name '*.sh'); do echo "Running ${F}" "${F}" "$@" diff --git a/hack/verify/shellcheck.sh b/hack/verify/shellcheck.sh new file mode 100755 index 0000000..9c84311 --- /dev/null +++ b/hack/verify/shellcheck.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +# Copyright 2026 Google LLC +# +# 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 -o nounset -o pipefail + +ROOT="$(git rev-parse --show-toplevel)" +cd "${ROOT}" + +./hack/third_party/kubernetes/verify-shellcheck.sh "$@"