Revision 1fdfb4292c2c31dc333b57b6a8e7b8c029efa597 authored by Benjamin Wang on 14 December 2022, 18:12:49 UTC, committed by Benjamin Wang on 14 December 2022, 18:12:49 UTC
In order to fix https://github.com/etcd-io/etcd/issues/12385, PR https://github.com/etcd-io/etcd/pull/14322 introduced a change in which the client side may retry based on the error message returned from server side. This is not good, as it's too fragile and it's also changed the protocol between client and server. Please see the discussion in https://github.com/kubernetes/kubernetes/pull/114403 Note: The issue https://github.com/etcd-io/etcd/issues/12385 only happens when auth is enabled, and client side reuse the same client to watch. So we decided to rollback the change on 3.5, reasons: 1.K8s doesn't enable auth at all. It has no any impact on K8s. 2.It's very easy for client application to workaround the issue. The client just needs to create a new client each time before watching. Signed-off-by: Benjamin Wang <wachao@vmware.com>
1 parent 127e9c0
test.sh
#!/usr/bin/env bash
#
# Run all etcd tests
# ./test
# ./test -v
#
#
# Run specified test pass
#
# $ PASSES=unit ./test
# $ PASSES=integration ./test
#
#
# Run tests for one package
# Each pass has different default timeout, if you just run tests in one package or 1 test case then you can set TIMEOUT
# flag for different expectation
#
# $ PASSES=unit PKG=./wal TIMEOUT=1m ./test
# $ PASSES=integration PKG=./clientv3 TIMEOUT=1m ./test
#
# Run specified unit tests in one package
# To run all the tests with prefix of "TestNew", set "TESTCASE=TestNew ";
# to run only "TestNew", set "TESTCASE="\bTestNew\b""
#
# $ PASSES=unit PKG=./wal TESTCASE=TestNew TIMEOUT=1m ./test
# $ PASSES=unit PKG=./wal TESTCASE="\bTestNew\b" TIMEOUT=1m ./test
# $ PASSES=integration PKG=./client/integration TESTCASE="\bTestV2NoRetryEOF\b" TIMEOUT=1m ./test
#
#
# Run code coverage
# COVERDIR must either be a absolute path or a relative path to the etcd root
# $ COVERDIR=coverage PASSES="build build_cov cov" ./test
# $ go tool cover -html ./coverage/cover.out
set -e
set -o pipefail
# Consider command as failed when any component of the pipe fails:
# https://stackoverflow.com/questions/1221833/pipe-output-and-capture-exit-status-in-bash
set -o pipefail
# The test script is not supposed to make any changes to the files
# e.g. add/update missing dependencies. Such divergences should be
# detected and trigger a failure that needs explicit developer's action.
export GOFLAGS=-mod=readonly
export ETCD_VERIFY=all
source ./scripts/test_lib.sh
source ./build.sh
PASSES=${PASSES:-"fmt bom dep build unit"}
PKG=${PKG:-}
if [ -z "$GOARCH" ]; then
GOARCH=$(go env GOARCH);
fi
# determine whether target supports race detection
if [ -z "${RACE}" ] ; then
if [ "$GOARCH" == "amd64" ]; then
RACE="--race"
else
RACE="--race=false"
fi
else
RACE="--race=${RACE:-true}"
fi
# This options make sense for cases where SUT (System Under Test) is compiled by test.
COMMON_TEST_FLAGS=("${RACE}")
if [[ -n "${CPU}" ]]; then
COMMON_TEST_FLAGS+=("--cpu=${CPU}")
fi
log_callout "Running with ${COMMON_TEST_FLAGS[*]}"
RUN_ARG=()
if [ -n "${TESTCASE}" ]; then
RUN_ARG=("-run=${TESTCASE}")
fi
function build_pass {
log_callout "Building etcd"
run_for_modules run go build "${@}" || return 2
GO_BUILD_FLAGS="-v" etcd_build "${@}"
GO_BUILD_FLAGS="-v" tools_build "${@}"
}
################# REGULAR TESTS ################################################
# run_unit_tests [pkgs] runs unit tests for a current module and givesn set of [pkgs]
function run_unit_tests {
local pkgs="${1:-./...}"
shift 1
# shellcheck disable=SC2086
GOLANG_TEST_SHORT=true go_test "${pkgs}" "parallel" : -short -timeout="${TIMEOUT:-3m}" "${COMMON_TEST_FLAGS[@]}" "${RUN_ARG[@]}" "$@"
}
function unit_pass {
run_for_modules run_unit_tests "$@"
}
function integration_extra {
if [ -z "${PKG}" ] ; then
run_for_module "." go_test "./contrib/raftexample" "keep_going" : -timeout="${TIMEOUT:-5m}" "${RUN_ARG[@]}" "${COMMON_TEST_FLAGS[@]}" "$@" || return $?
run_for_module "tests" go_test "./integration/v2store/..." "keep_going" : -tags v2v3 -timeout="${TIMEOUT:-5m}" "${RUN_ARG[@]}" "${COMMON_TEST_FLAGS[@]}" "$@" || return $?
else
log_warning "integration_extra ignored when PKG is specified"
fi
}
function integration_pass {
local pkgs=${USERPKG:-"./integration/..."}
run_for_module "tests" go_test "${pkgs}" "parallel" : -timeout="${TIMEOUT:-15m}" "${COMMON_TEST_FLAGS[@]}" "${RUN_ARG[@]}" "$@" || return $?
integration_extra "$@"
}
function e2e_pass {
# e2e tests are running pre-build binary. Settings like --race,-cover,-cpu does not have any impact.
run_for_module "tests" go_test "./e2e/..." "keep_going" : -timeout="${TIMEOUT:-30m}" "${RUN_ARG[@]}" "$@"
}
function integration_e2e_pass {
run_pass "integration" "${@}"
run_pass "e2e" "${@}"
}
# generic_checker [cmd...]
# executes given command in the current module, and clearly fails if it
# failed or returned output.
function generic_checker {
local cmd=("$@")
if ! output=$("${cmd[@]}"); then
echo "${output}"
log_error -e "FAIL: '${cmd[*]}' checking failed (!=0 return code)"
return 255
fi
if [ -n "${output}" ]; then
echo "${output}"
log_error -e "FAIL: '${cmd[*]}' checking failed (printed output)"
return 255
fi
}
function functional_pass {
run ./tests/functional/build
# Clean up any data and logs from previous runs
rm -rf /tmp/etcd-functional-* /tmp/etcd-functional-*.backup
# TODO: These ports should be dynamically allocated instead of hard-coded.
for a in 1 2 3; do
./bin/etcd-agent --network tcp --address 127.0.0.1:${a}9027 < /dev/null &
pid="$!"
agent_pids="${agent_pids} $pid"
done
for a in 1 2 3; do
log_callout "Waiting for 'etcd-agent' on ${a}9027..."
while ! nc -z localhost ${a}9027; do
sleep 1
done
done
log_callout "functional test START!"
run ./bin/etcd-tester --config ./tests/functional/functional.yaml && log_success "'etcd-tester' succeeded"
local etcd_tester_exit_code=$?
if [[ "${etcd_tester_exit_code}" -ne "0" ]]; then
log_error "ETCD_TESTER_EXIT_CODE:" ${etcd_tester_exit_code}
fi
# shellcheck disable=SC2206
agent_pids=($agent_pids)
kill -s TERM "${agent_pids[@]}" || true
if [[ "${etcd_tester_exit_code}" -ne "0" ]]; then
log_error -e "\\nFAILED! 'tail -1000 /tmp/etcd-functional-1/etcd.log'"
tail -1000 /tmp/etcd-functional-1/etcd.log
log_error -e "\\nFAILED! 'tail -1000 /tmp/etcd-functional-2/etcd.log'"
tail -1000 /tmp/etcd-functional-2/etcd.log
log_error -e "\\nFAILED! 'tail -1000 /tmp/etcd-functional-3/etcd.log'"
tail -1000 /tmp/etcd-functional-3/etcd.log
log_error "--- FAIL: exit code" ${etcd_tester_exit_code}
return ${etcd_tester_exit_code}
fi
log_success "functional test PASS!"
}
function grpcproxy_pass {
run_for_module "tests" go_test "./integration/... ./e2e" "fail_fast" : \
-timeout=30m -tags cluster_proxy "${COMMON_TEST_FLAGS[@]}" "$@"
}
################# COVERAGE #####################################################
# Builds artifacts used by tests/e2e in coverage mode.
function build_cov_pass {
run_for_module "server" run go test -tags cov -c -covermode=set -coverpkg="./..." -o "../bin/etcd_test"
run_for_module "etcdctl" run go test -tags cov -c -covermode=set -coverpkg="./..." -o "../bin/etcdctl_test"
run_for_module "etcdutl" run go test -tags cov -c -covermode=set -coverpkg="./..." -o "../bin/etcdutl_test"
}
# pkg_to_coverflag [prefix] [pkgs]
# produces name of .coverprofile file to be used for tests of this package
function pkg_to_coverprofileflag {
local prefix="${1}"
local pkgs="${2}"
local pkgs_normalized
prefix_normalized=$(echo "${prefix}" | tr "./ " "__+")
if [ "${pkgs}" == "./..." ]; then
pkgs_normalized="all"
else
pkgs_normalized=$(echo "${pkgs}" | tr "./ " "__+")
fi
mkdir -p "${coverdir}/${prefix_normalized}"
echo -n "-coverprofile=${coverdir}/${prefix_normalized}/${pkgs_normalized}.coverprofile"
}
function not_test_packages {
for m in $(modules); do
if [[ $m =~ .*/etcd/tests/v3 ]]; then continue; fi
if [[ $m =~ .*/etcd/v3 ]]; then continue; fi
echo "${m}/..."
done
}
# split_dir [dir] [num]
function split_dir {
local d="${1}"
local num="${2}"
local i=0
for f in "${d}/"*; do
local g=$(( "${i}" % "${num}" ))
mkdir -p "${d}_${g}"
mv "${f}" "${d}_${g}/"
(( i++ ))
done
}
function split_dir_pass {
split_dir ./covdir/integration 4
}
# merge_cov_files [coverdir] [outfile]
# merges all coverprofile files into a single file in the given directory.
function merge_cov_files {
local coverdir="${1}"
local cover_out_file="${2}"
log_callout "Merging coverage results in: ${coverdir}"
# gocovmerge requires not-empty test to start with:
echo "mode: set" > "${cover_out_file}"
local i=0
local count
count=$(find "${coverdir}"/*.coverprofile | wc -l)
for f in "${coverdir}"/*.coverprofile; do
# print once per 20 files
if ! (( "${i}" % 20 )); then
log_callout "${i} of ${count}: Merging file: ${f}"
fi
run_go_tool "github.com/gyuho/gocovmerge" "${f}" "${cover_out_file}" > "${coverdir}/cover.tmp" 2>/dev/null
if [ -s "${coverdir}"/cover.tmp ]; then
mv "${coverdir}/cover.tmp" "${cover_out_file}"
fi
(( i++ ))
done
}
# merge_cov [coverdir]
function merge_cov {
log_callout "[$(date)] Merging coverage files ..."
coverdir="${1}"
for d in "${coverdir}"/*/; do
d=${d%*/} # remove the trailing "/"
merge_cov_files "${d}" "${d}.coverprofile" &
done
wait
merge_cov_files "${coverdir}" "${coverdir}/all.coverprofile"
}
function cov_pass {
# shellcheck disable=SC2153
if [ -z "$COVERDIR" ]; then
log_error "COVERDIR undeclared"
return 255
fi
if [ ! -f "bin/etcd_test" ]; then
log_error "etcd_test binary not found. Call: PASSES='build_cov' ./test"
return 255
fi
local coverdir
coverdir=$(readlink -f "${COVERDIR}")
mkdir -p "${coverdir}"
find "${coverdir}" -print0 -name '*.coverprofile' | xargs -0 rm
local covpkgs
covpkgs=$(not_test_packages)
local coverpkg_comma
coverpkg_comma=$(echo "${covpkgs[@]}" | xargs | tr ' ' ',')
local gocov_build_flags=("-covermode=set" "-coverpkg=$coverpkg_comma")
local failed=""
log_callout "[$(date)] Collecting coverage from unit tests ..."
for m in $(module_dirs); do
GOLANG_TEST_SHORT=true run_for_module "${m}" go_test "./..." "parallel" "pkg_to_coverprofileflag unit_${m}" -short -timeout=30m \
"${gocov_build_flags[@]}" "$@" || failed="$failed unit"
done
log_callout "[$(date)] Collecting coverage from integration tests ..."
run_for_module "tests" go_test "./integration/..." "parallel" "pkg_to_coverprofileflag integration" \
-timeout=30m "${gocov_build_flags[@]}" "$@" || failed="$failed integration"
# integration-store-v2
run_for_module "tests" go_test "./integration/v2store/..." "keep_going" "pkg_to_coverprofileflag store_v2" \
-tags v2v3 -timeout=5m "${gocov_build_flags[@]}" "$@" || failed="$failed integration_v2v3"
# integration_cluster_proxy
run_for_module "tests" go_test "./integration/..." "parallel" "pkg_to_coverprofileflag integration_cluster_proxy" \
-tags cluster_proxy -timeout=5m "${gocov_build_flags[@]}" || failed="$failed integration_cluster_proxy"
log_callout "[$(date)] Collecting coverage from e2e tests ..."
# We don't pass 'gocov_build_flags' nor 'pkg_to_coverprofileflag' here,
# as the coverage is collected from the ./bin/etcd_test & ./bin/etcdctl_test internally spawned.
mkdir -p "${coverdir}/e2e"
COVERDIR="${coverdir}/e2e" run_for_module "tests" go_test "./e2e/..." "keep_going" : -tags=cov -timeout 30m "$@" || failed="$failed tests_e2e"
split_dir "${coverdir}/e2e" 10
log_callout "[$(date)] Collecting coverage from e2e tests with proxy ..."
mkdir -p "${coverdir}/e2e_proxy"
COVERDIR="${coverdir}/e2e_proxy" run_for_module "tests" go_test "./e2e/..." "keep_going" : -tags="cov cluster_proxy" -timeout 30m "$@" || failed="$failed tests_e2e_proxy"
split_dir "${coverdir}/e2e_proxy" 10
local cover_out_file="${coverdir}/all.coverprofile"
merge_cov "${coverdir}"
# strip out generated files (using GNU-style sed)
sed --in-place -E "/[.]pb[.](gw[.])?go/d" "${cover_out_file}" || true
sed --in-place -E "s|go.etcd.io/etcd/api/v3/|api/|g" "${cover_out_file}" || true
sed --in-place -E "s|go.etcd.io/etcd/client/v3/|client/v3/|g" "${cover_out_file}" || true
sed --in-place -E "s|go.etcd.io/etcd/client/v2/|client/v2/|g" "${cover_out_file}" || true
sed --in-place -E "s|go.etcd.io/etcd/client/pkg/v3|client/pkg/v3/|g" "${cover_out_file}" || true
sed --in-place -E "s|go.etcd.io/etcd/etcdctl/v3/|etcdctl/|g" "${cover_out_file}" || true
sed --in-place -E "s|go.etcd.io/etcd/etcdutl/v3/|etcdutl/|g" "${cover_out_file}" || true
sed --in-place -E "s|go.etcd.io/etcd/pkg/v3/|pkg/|g" "${cover_out_file}" || true
sed --in-place -E "s|go.etcd.io/etcd/raft/v3/|raft/|g" "${cover_out_file}" || true
sed --in-place -E "s|go.etcd.io/etcd/server/v3/|server/|g" "${cover_out_file}" || true
# held failures to generate the full coverage file, now fail
if [ -n "$failed" ]; then
for f in $failed; do
log_error "--- FAIL:" "$f"
done
log_warning "Despite failures, you can see partial report:"
log_warning " go tool cover -html ${cover_out_file}"
return 255
fi
log_success "done :) [see report: go tool cover -html ${cover_out_file}]"
}
######### Code formatting checkers #############################################
function fmt_pass {
toggle_failpoints disable
# TODO: add "unparam","staticcheck", "unconvert", "ineffasign","nakedret"
# after resolving ore-existing errors.
# markdown_you - too sensitive check was temporarilly disbled.
for p in shellcheck \
goword \
gofmt \
govet \
revive \
license_header \
receiver_name \
mod_tidy \
dep \
shellcheck \
shellws \
; do
run_pass "${p}" "${@}"
done
}
function shellcheck_pass {
if tool_exists "shellcheck" "https://github.com/koalaman/shellcheck#installing"; then
generic_checker run shellcheck -fgcc build test scripts/*.sh ./*.sh
fi
}
function shellws_pass {
TAB=$'\t'
log_callout "Ensuring no tab-based indention in shell scripts"
local files
files=$(find ./ -name '*.sh' -print0 | xargs -0 )
# shellcheck disable=SC2206
files=( ${files[@]} "./scripts/build-binary" "./scripts/build-docker" "./scripts/release" )
log_cmd "grep -E -n $'^ *${TAB}' ${files[*]}"
# shellcheck disable=SC2086
if grep -E -n $'^ *${TAB}' "${files[@]}" | sed $'s|${TAB}|[\\\\tab]|g'; then
log_error "FAIL: found tab-based indention in bash scripts. Use ' ' (double space)."
local files_with_tabs
files_with_tabs=$(grep -E -l $'^ *\\t' "${files[@]}")
log_warning "Try: sed -i 's|\\t| |g' $files_with_tabs"
return 1
else
log_success "SUCCESS: no tabulators found."
return 0
fi
}
function markdown_you_find_eschew_you {
local find_you_cmd="find . -name \\*.md ! -path '*/vendor/*' ! -path './Documentation/*' ! -path './gopath.proto/*' ! -path './release/*' -exec grep -E --color '[Yy]ou[r]?[ '\\''.,;]' {} + || true"
run eval "${find_you_cmd}"
}
function markdown_you_pass {
generic_checker markdown_you_find_eschew_you
}
function markdown_marker_pass {
# TODO: check other markdown files when marker handles headers with '[]'
if tool_exists "marker" "https://crates.io/crates/marker"; then
generic_checker run marker --skip-http --root ./Documentation 2>&1
fi
}
function govet_pass {
run_for_modules generic_checker run go vet
}
function govet_shadow_pass {
local shadow
shadow=$(tool_get_bin "golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow")
run_for_modules generic_checker run go vet -all -vettool="${shadow}"
}
function unparam_pass {
run_for_modules generic_checker run_go_tool "mvdan.cc/unparam"
}
function staticcheck_pass {
run_for_modules generic_checker run_go_tool "honnef.co/go/tools/cmd/staticcheck"
}
function revive_pass {
run_for_modules generic_checker run_go_tool "github.com/mgechev/revive" -config "${ETCD_ROOT_DIR}/tests/revive.toml" -exclude "vendor/..."
}
function unconvert_pass {
run_for_modules generic_checker run_go_tool "github.com/mdempsky/unconvert" unconvert -v
}
function ineffassign_per_package {
# bash 3.x compatible replacement of: mapfile -t gofiles < <(go_srcs_in_module "$1")
local gofiles=()
while IFS= read -r line; do gofiles+=("$line"); done < <(go_srcs_in_module "$1")
run_go_tool github.com/gordonklaus/ineffassign "${gofiles[@]}"
}
function ineffassign_pass {
run_for_modules generic_checker ineffassign_per_package
}
function nakedret_pass {
run_for_modules generic_checker run_go_tool "github.com/alexkohler/nakedret"
}
function license_header_pass {
# bash 3.x compatible replacement of: mapfile -t gofiles < <(go_srcs_in_module "$1")
local gofiles=()
while IFS= read -r line; do gofiles+=("$line"); done < <(go_srcs_in_module "$1")
for file in "${gofiles[@]}"; do
if ! head -n3 "${file}" | grep -Eq "(Copyright|generated|GENERATED)" ; then
licRes="${licRes}"$(echo -e " ${file}")
fi
done
if [ -n "${licRes}" ]; then
log_error -e "license header checking failed:\\n${licRes}"
return 255
fi
}
function receiver_name_for_package {
# bash 3.x compatible replacement of: mapfile -t gofiles < <(go_srcs_in_module "$1")
local gofiles=()
while IFS= read -r line; do gofiles+=("$line"); done < <(go_srcs_in_module "$1")
recvs=$(grep 'func ([^*]' "${gofiles[@]}" | tr ':' ' ' | \
awk ' { print $2" "$3" "$4" "$1 }' | sed "s/[a-zA-Z\\.]*go//g" | sort | uniq | \
grep -Ev "(Descriptor|Proto|_)" | awk ' { print $3" "$4 } ' | sort | uniq -c | grep -v ' 1 ' | awk ' { print $2 } ')
if [ -n "${recvs}" ]; then
# shellcheck disable=SC2206
recvs=($recvs)
for recv in "${recvs[@]}"; do
log_error "Mismatched receiver for $recv..."
grep "$recv" "${gofiles[@]}" | grep 'func ('
done
return 255
fi
}
function receiver_name_pass {
run_for_modules receiver_name_for_package
}
# goword_for_package package
# checks spelling and comments in the 'package' in the current module
#
function goword_for_package {
# bash 3.x compatible replacement of: mapfile -t gofiles < <(go_srcs_in_module "$1")
local gofiles=()
while IFS= read -r line; do gofiles+=("$line"); done < <(go_srcs_in_module "$1")
local gowordRes
# spellchecking can be enabled with GOBINARGS="--tags=spell"
# but it requires heavy dependencies installation, like:
# apt-get install libaspell-dev libhunspell-dev hunspell-en-us aspell-en
# only check for broke exported godocs
if gowordRes=$(run_go_tool "github.com/chzchzchz/goword" -use-spell=false "${gofiles[@]}" | grep godoc-export | sort); then
log_error -e "goword checking failed:\\n${gowordRes}"
return 255
fi
if [ -n "$gowordRes" ]; then
log_error -e "goword checking returned output:\\n${gowordRes}"
return 255
fi
}
function goword_pass {
run_for_modules goword_for_package || return 255
}
function go_fmt_for_package {
# We utilize 'go fmt' to find all files suitable for formatting,
# but reuse full power gofmt to perform just RO check.
go fmt -n "$1" | sed 's| -w | -d |g' | sh
}
function gofmt_pass {
run_for_modules generic_checker go_fmt_for_package
}
function bom_pass {
log_callout "Checking bill of materials..."
# https://github.com/golang/go/commit/7c388cc89c76bc7167287fb488afcaf5a4aa12bf
# shellcheck disable=SC2207
modules=($(modules_exp))
# Internally license-bill-of-materials tends to modify go.sum
run cp go.sum go.sum.tmp || return 2
run cp go.mod go.mod.tmp || return 2
output=$(GOFLAGS=-mod=mod run_go_tool github.com/coreos/license-bill-of-materials \
--override-file ./bill-of-materials.override.json \
"${modules[@]}")
code="$?"
run cp go.sum.tmp go.sum || return 2
run cp go.mod.tmp go.mod || return 2
if [ "${code}" -ne 0 ] ; then
log_error -e "license-bill-of-materials (code: ${code}) failed with:\\n${output}"
return 255
else
echo "${output}" > "bom-now.json.tmp"
fi
if ! diff ./bill-of-materials.json bom-now.json.tmp; then
log_error "modularized licenses do not match given bill of materials"
return 255
fi
rm bom-now.json.tmp
}
######## VARIOUS CHECKERS ######################################################
function dump_deps_of_module() {
local module
if ! module=$(run go list -m); then
return 255
fi
run go list -f "{{if not .Indirect}}{{if .Version}}{{.Path}},{{.Version}},${module}{{end}}{{end}}" -m all
}
# Checks whether dependencies are consistent across modules
function dep_pass {
local all_dependencies
all_dependencies=$(run_for_modules dump_deps_of_module | sort) || return 2
local duplicates
duplicates=$(echo "${all_dependencies}" | cut -d ',' -f 1,2 | sort | uniq | cut -d ',' -f 1 | sort | uniq -d) || return 2
for dup in ${duplicates}; do
log_error "FAIL: inconsistent versions for depencency: ${dup}"
echo "${all_dependencies}" | grep "${dup}" | sed "s|\\([^,]*\\),\\([^,]*\\),\\([^,]*\\)| - \\1@\\2 from: \\3|g"
done
if [[ -n "${duplicates}" ]]; then
log_error "FAIL: inconsistent dependencies"
return 2
else
log_success "SUCCESS: dependencies are consistent across modules"
fi
}
function release_pass {
rm -f ./bin/etcd-last-release
# to grab latest patch release; bump this up for every minor release
UPGRADE_VER=$(git tag -l --sort=-version:refname "v3.4.*" | head -1)
if [ -n "$MANUAL_VER" ]; then
# in case, we need to test against different version
UPGRADE_VER=$MANUAL_VER
fi
if [[ -z ${UPGRADE_VER} ]]; then
UPGRADE_VER="v3.3.0"
log_warning "fallback to" ${UPGRADE_VER}
fi
local file="etcd-$UPGRADE_VER-linux-$GOARCH.tar.gz"
log_callout "Downloading $file"
set +e
curl --fail -L "https://github.com/etcd-io/etcd/releases/download/$UPGRADE_VER/$file" -o "/tmp/$file"
local result=$?
set -e
case $result in
0) ;;
*) log_error "--- FAIL:" ${result}
return $result
;;
esac
tar xzvf "/tmp/$file" -C /tmp/ --strip-components=1
mkdir -p ./bin
mv /tmp/etcd ./bin/etcd-last-release
}
function mod_tidy_for_module {
# Watch for upstream solution: https://github.com/golang/go/issues/27005
local tmpModDir
tmpModDir=$(mktemp -d -t 'tmpModDir.XXXXXX')
run cp "./go.mod" "${tmpModDir}" || return 2
# Guarantees keeping go.sum minimal
# If this is causing too much problems, we should
# stop controlling go.sum at all.
rm go.sum
run go mod tidy || return 2
set +e
local tmpFileGoModInSync
diff -C 5 "${tmpModDir}/go.mod" "./go.mod"
tmpFileGoModInSync="$?"
# Bring back initial state
mv "${tmpModDir}/go.mod" "./go.mod"
if [ "${tmpFileGoModInSync}" -ne 0 ]; then
log_error "${PWD}/go.mod is not in sync with 'go mod tidy'"
return 255
fi
}
function mod_tidy_pass {
run_for_modules mod_tidy_for_module
}
########### MAIN ###############################################################
function run_pass {
local pass="${1}"
shift 1
log_callout -e "\\n'${pass}' started at $(date)"
if "${pass}_pass" "$@" ; then
log_success "'${pass}' completed at $(date)"
else
log_error "FAIL: '${pass}' failed at $(date)"
exit 255
fi
}
log_callout "Starting at: $(date)"
for pass in $PASSES; do
run_pass "${pass}" "${@}"
done
log_success "SUCCESS"
![swh spinner](/static/img/swh-spinner.gif)
Computing file changes ...