In the past we had connecitivity issues on Saucelabs. Browsers on mobile devices were not able to properly resolve the `localhost` hostname through the tunnel. This is because the device resolves `localhost` or `127.0.0.1` to the actual Saucelabs device, while it should resolve to the tunnel host machine (in our case the CircleCI VM). In the past, we simply disabled the failing devices and re-enabled the devices later. At this point, the Saucelabs team claimed that the connecitivy/proxy issues were fixed. Saucelabs seems to have a process for VMs which ensures that requests to `localhost` / `127.0.0.1` are properly resolved through the tunnel. This process is not very reliable and can cause tests to fail. Related issues have been observed/mentioned in the Saucelabs support docs. e.g. https://support.saucelabs.com/hc/en-us/articles/115002212447-Unable-to-Reach-Application-on-localhost-for-Tests-Run-on-Safari-8-and-9-and-Edge https://support.saucelabs.com/hc/en-us/articles/225106887-Safari-and-Internet-Explorer-Won-t-Load-Website-When-Using-Sauce-Connect-on-Localhost In order to ensure that requests are always resolved through the tunnel, we add our own domain alias in the CircleCI's hosts file, and enforce that it is always resolved through the tunnel (using the `--tunnel-domains` SC flag). Saucelabs devices by default will never resolve this domain/hostname to the actual local Saucelabs device. PR Close #35171
430 lines
12 KiB
Bash
Executable File
430 lines
12 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
set -u -e -o pipefail
|
|
|
|
####################################################################################################
|
|
# Some helper funtions
|
|
|
|
@echo() {
|
|
echo "# $*"
|
|
}
|
|
|
|
@warn() {
|
|
@echo "Warning: $*" >&2
|
|
}
|
|
|
|
@fail() {
|
|
@echo "Error! $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
@remove() {
|
|
local f="$1"
|
|
if [[ -f ${f} ]]; then
|
|
@echo "Removing ${f}"
|
|
rm -f "${f}" || @fail "Can not delete ${f} file"
|
|
fi
|
|
}
|
|
|
|
@kill() {
|
|
for p in $1; do
|
|
if kill -0 ${p} >/dev/null 2>&1; then
|
|
kill ${p}
|
|
sleep 2
|
|
if kill -0 ${p} >/dev/null 2>&1; then
|
|
kill -9 ${p}
|
|
sleep 2
|
|
fi
|
|
fi
|
|
done
|
|
}
|
|
|
|
@wait_for() {
|
|
local m="$1"
|
|
local f="$2"
|
|
if [[ ! -f "${f}" ]]; then
|
|
printf "# ${m} (${f})"
|
|
while [[ ! -f "${f}" ]]; do
|
|
printf "."
|
|
sleep 0.5
|
|
done
|
|
printf "\n"
|
|
fi
|
|
}
|
|
|
|
####################################################################################################
|
|
# Sauce service functions
|
|
|
|
readonly SCRIPT_DIR=$(cd $(dirname $0); pwd)
|
|
readonly TMP_DIR="/tmp/angular/sauce-service"
|
|
mkdir -p ${TMP_DIR}
|
|
|
|
# Location for the saucelabs log file.
|
|
readonly SAUCE_LOG_FILE="${TMP_DIR}/sauce-connect.log"
|
|
|
|
# Location for the saucelabs ready to connection process id lock file.
|
|
readonly SAUCE_PID_FILE="${TMP_DIR}/sauce-connect.pid"
|
|
|
|
# Location for the saucelabs ready to connect lock file.
|
|
readonly SAUCE_READY_FILE="${TMP_DIR}/sauce-connect.lock"
|
|
|
|
# Location for the saucelabs params file for use by test runner.
|
|
readonly SAUCE_PARAMS_JSON_FILE="${TMP_DIR}/sauce-connect-params.json"
|
|
|
|
# Amount of seconds we wait for sauceconnect to establish a tunnel instance. In order to not
|
|
# acquire CircleCI instances for too long if sauceconnect fails, we need a connect timeout.
|
|
readonly SAUCE_READY_FILE_TIMEOUT=120
|
|
|
|
readonly SERVICE_LOCK_FILE="${TMP_DIR}/service.lock"
|
|
readonly SERVICE_START_FILE="${TMP_DIR}/service.start"
|
|
readonly SERVICE_PID_FILE="${TMP_DIR}/service.pid"
|
|
readonly SERVICE_LOG_FILE="${TMP_DIR}/service.log"
|
|
|
|
service-setup-command() {
|
|
if [[ -z "${SAUCE_USERNAME:-}" ]]; then
|
|
@fail "SAUCE_USERNAME environment variable required"
|
|
fi
|
|
|
|
if [[ -z "${SAUCE_ACCESS_KEY:-}" ]]; then
|
|
@fail "SAUCE_ACCESS_KEY environment variable required"
|
|
fi
|
|
|
|
if [[ -z "${SAUCE_TUNNEL_IDENTIFIER:-}" ]]; then
|
|
@fail "SAUCE_TUNNEL_IDENTIFIER environment variable required"
|
|
fi
|
|
|
|
local unameOut="$(uname -s)"
|
|
case "${unameOut}" in
|
|
Linux*) local machine=linux ;;
|
|
Darwin*) local machine=darwin ;;
|
|
CYGWIN*) local machine=windows ;;
|
|
MINGW*) local machine=windows ;;
|
|
MSYS_NT*) local machine=windows ;;
|
|
*) local machine=linux
|
|
printf "\nUnrecongized uname '${unameOut}'; defaulting to use node for linux.\n" >&2
|
|
printf "Please file an issue to https://github.com/bazelbuild/rules_nodejs/issues if \n" >&2
|
|
printf "you would like to add your platform to the supported rules_nodejs node platforms.\n\n" >&2
|
|
;;
|
|
esac
|
|
|
|
case "${machine}" in
|
|
# Path to sauce connect executable
|
|
linux)
|
|
if [[ -z "${BUILD_WORKSPACE_DIRECTORY:-}" ]]; then
|
|
# Started manually
|
|
SAUCE_CONNECT="${SCRIPT_DIR}/../../node_modules/sauce-connect/bin/sc"
|
|
else
|
|
# Started via `bazel run`
|
|
SAUCE_CONNECT="${BUILD_WORKSPACE_DIRECTORY}/node_modules/sauce-connect/bin/sc"
|
|
fi
|
|
;;
|
|
*)
|
|
if [[ -z "${SAUCE_CONNECT:-}" ]]; then
|
|
@fail "SAUCE_CONNECT environment variable is required on non-linux environments"
|
|
exit 1
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
if [[ ! -f ${SAUCE_CONNECT} ]]; then
|
|
@fail "sc binary not found at ${SAUCE_CONNECT}"
|
|
fi
|
|
|
|
echo "{ \"SAUCE_USERNAME\": \"${SAUCE_USERNAME}\", \"SAUCE_ACCESS_KEY\": \"${SAUCE_ACCESS_KEY}\", \"SAUCE_TUNNEL_IDENTIFIER\": \"${SAUCE_TUNNEL_IDENTIFIER}\", \"SAUCE_LOCALHOST_ALIAS_DOMAIN\": \"${SAUCE_LOCALHOST_ALIAS_DOMAIN}\" }" > ${SAUCE_PARAMS_JSON_FILE}
|
|
|
|
# Command arguments that will be passed to sauce-connect.
|
|
# By default we disable SSL bumping for all requests. This is because SSL bumping is
|
|
# not needed for our test setup and in order to perform the SSL bumping, Saucelabs
|
|
# intercepts all HTTP requests in the tunnel VM and modifies them. This can cause
|
|
# flakiness as it makes all requests dependent on the SSL bumping middleware.
|
|
# See: https://wiki.saucelabs.com/display/DOCS/Troubleshooting+Sauce+Connect#TroubleshootingSauceConnect-DisablingSSLBumping
|
|
local sauce_args=(
|
|
"--no-ssl-bump-domains all"
|
|
"--logfile ${SAUCE_LOG_FILE}"
|
|
"--pidfile ${SAUCE_PID_FILE}"
|
|
"--readyfile ${SAUCE_READY_FILE}"
|
|
"--tunnel-identifier ${SAUCE_TUNNEL_IDENTIFIER}"
|
|
"--user ${SAUCE_USERNAME}"
|
|
# Don't add the --api-key here so we don't echo it out in service-pre-start
|
|
)
|
|
|
|
if [[ -n "${SAUCE_LOCALHOST_ALIAS_DOMAIN}" ]]; then
|
|
# Ensures that requests to the localhost alias domain are always resolved through the tunnel.
|
|
# This environment variable is usually configured on CI, and refers to a domain that has been
|
|
# locally configured in the current machine's hosts file (e.g. `/etc/hosts`). The domain should
|
|
# resolve to the current machine in Saucelabs VMs, so we need to ensure that it is resolved
|
|
# through the tunnel we going to create.
|
|
sauce_args+=("--tunnel-domains ${SAUCE_LOCALHOST_ALIAS_DOMAIN}")
|
|
fi
|
|
|
|
@echo "Sauce connect will be started with:"
|
|
echo " ${SAUCE_CONNECT} ${sauce_args[@]}"
|
|
SERVICE_COMMAND="${SAUCE_CONNECT} ${sauce_args[@]} --api-key ${SAUCE_ACCESS_KEY}"
|
|
}
|
|
|
|
# Called by pre-start & post-stop
|
|
service-cleanup() {
|
|
if [[ -f "${SAUCE_PID_FILE}" ]]; then
|
|
local p=$(cat "${SAUCE_PID_FILE}")
|
|
@echo "Stopping Sauce Connect (pid $p)..."
|
|
@kill $p
|
|
fi
|
|
@remove "${SAUCE_PID_FILE}"
|
|
@remove "${SAUCE_READY_FILE}"
|
|
@remove "${SAUCE_PARAMS_JSON_FILE}"
|
|
}
|
|
|
|
# Called before service is setup
|
|
service-pre-setup() {
|
|
service-cleanup
|
|
}
|
|
|
|
# Called after service is setup
|
|
service-post-setup() {
|
|
@echo " sauce params : ${SAUCE_PARAMS_JSON_FILE}"
|
|
}
|
|
|
|
# Called before service is started
|
|
service-pre-start() {
|
|
return
|
|
}
|
|
|
|
# Called after service is started
|
|
service-post-start() {
|
|
@wait_for "Waiting for Sauce Connect Proxy process" "${SAUCE_PID_FILE}"
|
|
@echo "Sauce Connect Proxy started (pid $(cat "${SAUCE_PID_FILE}"))"
|
|
}
|
|
|
|
# Called if service fails to start
|
|
service-failed-setup() {
|
|
if [[ -f "${SERVICE_LOG_FILE}" ]]; then
|
|
echo "================================================================================"
|
|
echo "${SERVICE_LOG_FILE}:"
|
|
echo $(cat "${SERVICE_LOG_FILE}")
|
|
fi
|
|
}
|
|
|
|
# Called by ready-wait action
|
|
service-ready-wait() {
|
|
if [[ ! -f "${SAUCE_PID_FILE}" ]]; then
|
|
@fail "Sauce Connect not running"
|
|
fi
|
|
if [[ ! -f "${SAUCE_READY_FILE}" ]]; then
|
|
# Wait for saucelabs tunnel to connect
|
|
printf "# Waiting for saucelabs tunnel to connect (${SAUCE_READY_FILE})"
|
|
counter=0
|
|
while [[ ! -f "${SAUCE_READY_FILE}" ]]; do
|
|
counter=$((counter + 1))
|
|
|
|
# Counter needs to be multiplied by two because the while loop only sleeps a half second.
|
|
# This has been made in favor of better progress logging (printing dots every half second)
|
|
if [ $counter -gt $[${SAUCE_READY_FILE_TIMEOUT} * 2] ]; then
|
|
@echo "Timed out after ${SAUCE_READY_FILE_TIMEOUT} seconds waiting for tunnel ready file."
|
|
if [[ -f "${SAUCE_LOG_FILE}" ]]; then
|
|
echo "================================================================================"
|
|
echo "${SAUCE_LOG_FILE}:"
|
|
cat "${SAUCE_LOG_FILE}"
|
|
fi
|
|
exit 5
|
|
fi
|
|
|
|
printf "."
|
|
sleep 0.5
|
|
done
|
|
printf "\n"
|
|
@echo "Saucelabs tunnel connected"
|
|
else
|
|
@echo "Saucelabs tunnel already connected"
|
|
fi
|
|
}
|
|
|
|
# Called before service is stopped
|
|
service-pre-stop() {
|
|
return
|
|
}
|
|
|
|
# Called after service is stopped
|
|
service-post-stop() {
|
|
service-cleanup
|
|
}
|
|
|
|
####################################################################################################
|
|
# Generic service functions
|
|
# This uses functions setup above but nothing below should be specific to saucelabs
|
|
|
|
@serviceLock() {
|
|
# Check is Lock File exists, if not create it and set trap on exit
|
|
printf "# Waiting for service action lock (${SERVICE_LOCK_FILE})"
|
|
while true; do
|
|
if { set -C; 2>/dev/null >"${SERVICE_LOCK_FILE}"; }; then
|
|
trap "rm -f \"${SERVICE_LOCK_FILE}\"" EXIT
|
|
printf "\n"
|
|
break
|
|
fi
|
|
printf "."
|
|
sleep 0.5
|
|
done
|
|
@echo "Acquired service action lock"
|
|
}
|
|
|
|
@serviceStatus() {
|
|
if [ -f "${SERVICE_PID_FILE}" ] && [ ! -z "$(cat "${SERVICE_PID_FILE}")" ]; then
|
|
local p=$(cat "${SERVICE_PID_FILE}")
|
|
|
|
if kill -0 $p >/dev/null 2>&1; then
|
|
@echo "Service is running (pid $p)"
|
|
return 0
|
|
else
|
|
@echo "Service is not running (process PID $p not exists)"
|
|
return 1
|
|
fi
|
|
else
|
|
@echo "Service is not running"
|
|
return 2
|
|
fi
|
|
}
|
|
|
|
@serviceSetup() {
|
|
if @serviceStatus >/dev/null 2>&1; then
|
|
@echo "Service already running (pid $(cat "${SERVICE_PID_FILE}"))"
|
|
return 0
|
|
fi
|
|
|
|
@echo "Setting up service..."
|
|
@remove "${SERVICE_PID_FILE}"
|
|
@remove "${SERVICE_START_FILE}"
|
|
touch "${SERVICE_LOG_FILE}" >/dev/null 2>&1 || @fail "Can not create ${SERVICE_LOG_FILE} file"
|
|
@echo " service pid : ${SERVICE_PID_FILE}"
|
|
@echo " service logs : ${SERVICE_LOG_FILE}"
|
|
service-pre-setup
|
|
service-setup-command
|
|
|
|
(
|
|
(
|
|
if [[ -z "${SERVICE_COMMAND:-}" ]]; then
|
|
@fail "No SERVICE_COMMAND is set"
|
|
fi
|
|
@wait_for "Waiting for start file" "${SERVICE_START_FILE}"
|
|
${SERVICE_COMMAND}
|
|
) >>"${SERVICE_LOG_FILE}" 2>&1
|
|
) &
|
|
echo $! >"${SERVICE_PID_FILE}"
|
|
|
|
if @serviceStatus >/dev/null 2>&1; then
|
|
@echo "Service setup (pid $(cat "${SERVICE_PID_FILE}"))"
|
|
service-post-setup
|
|
else
|
|
@echo "Error setting up Service!"
|
|
service-failed-setup
|
|
exit 1
|
|
fi
|
|
|
|
return $?
|
|
}
|
|
|
|
@serviceStart() {
|
|
if @serviceStatus >/dev/null 2>&1; then
|
|
@echo "Service already setup (pid $(cat "${SERVICE_PID_FILE}"))"
|
|
else
|
|
@serviceSetup
|
|
fi
|
|
if [[ -f "${SERVICE_START_FILE}" ]]; then
|
|
@echo "Service already started"
|
|
else
|
|
@echo "Starting service..."
|
|
service-pre-start
|
|
touch "${SERVICE_START_FILE}" >/dev/null 2>&1 || @err "Can not create ${SERVICE_START_FILE} file"
|
|
service-post-start
|
|
@echo "Service started"
|
|
fi
|
|
}
|
|
|
|
@serviceStop() {
|
|
if @serviceStatus >/dev/null 2>&1; then
|
|
touch "${SERVICE_PID_FILE}" >/dev/null 2>&1 || @fail "Can not touch ${SERVICE_PID_FILE} file"
|
|
|
|
service-pre-stop
|
|
@echo "Stopping sevice (pid $(cat "${SERVICE_PID_FILE}"))..."
|
|
@kill $(cat "${SERVICE_PID_FILE}")
|
|
|
|
if @serviceStatus >/dev/null 2>&1; then
|
|
@fail "Error stopping Service! Service already running with PID $(cat "${SERVICE_PID_FILE}")"
|
|
else
|
|
@echo "Service stopped"
|
|
@remove "${SERVICE_PID_FILE}"
|
|
@remove "${SERVICE_START_FILE}"
|
|
service-post-stop
|
|
fi
|
|
|
|
return 0
|
|
else
|
|
@warn "Service is not running"
|
|
service-post-stop
|
|
fi
|
|
}
|
|
|
|
@serviceStartReadyWait() {
|
|
@serviceStart
|
|
@serviceReadyWait
|
|
}
|
|
|
|
@serviceReadyWait() {
|
|
service-ready-wait
|
|
}
|
|
|
|
@serviceRestart() {
|
|
@serviceStop
|
|
@serviceStart
|
|
}
|
|
|
|
@serviceTail() {
|
|
tail -f "${SERVICE_LOG_FILE}"
|
|
}
|
|
|
|
case "${1:-}" in
|
|
setup)
|
|
@serviceLock
|
|
@serviceSetup
|
|
;;
|
|
start)
|
|
@serviceLock
|
|
@serviceStart
|
|
;;
|
|
start-ready-wait)
|
|
@serviceLock
|
|
@serviceStartReadyWait
|
|
;;
|
|
ready-wait)
|
|
@serviceLock
|
|
@serviceReadyWait
|
|
;;
|
|
stop)
|
|
@serviceLock
|
|
@serviceStop
|
|
;;
|
|
restart)
|
|
@serviceLock
|
|
@serviceRestart
|
|
;;
|
|
status)
|
|
@serviceLock
|
|
@serviceStatus
|
|
;;
|
|
run)
|
|
(
|
|
service-setup-command
|
|
if [[ -z "${SERVICE_COMMAND:-}" ]]; then
|
|
@fail "No SERVICE_COMMAND is set"
|
|
fi
|
|
${SERVICE_COMMAND}
|
|
)
|
|
;;
|
|
tail)
|
|
@serviceTail
|
|
;;
|
|
*)
|
|
@echo "Actions: [setup|start|start-read-wait|ready-wait|stop|restart|status|run|tail]"
|
|
exit 1
|
|
;;
|
|
esac
|