#!/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() { if [[ ! -f "${SAUCE_PID_FILE}" ]]; then printf "# Waiting for Sauce Connect Proxy process (${SAUCE_PID_FILE})" while [[ ! -f "${SAUCE_PID_FILE}" ]]; do if ! @serviceStatus >/dev/null 2>&1; then printf "\n" @serviceStop @echo "Service failed to start!" service-failed-setup exit 1 fi printf "." sleep 0.5 done printf "\n" fi @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 "tail ${SERVICE_LOG_FILE}:" echo "--------------------------------------------------------------------------------" tail "${SERVICE_LOG_FILE}" echo "--------------------------------------------------------------------------------" echo "^^^^^ ${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" @remove "${SERVICE_PID_FILE}" @remove "${SERVICE_START_FILE}" service-post-stop fi } @serviceStartReadyWait() { @serviceStart @serviceReadyWait } @serviceReadyWait() { service-ready-wait } @serviceRestart() { @serviceStop @serviceStart } @serviceTail() { @echo "tail ${SERVICE_LOG_FILE}:" tail -f "${SERVICE_LOG_FILE}" } @serviceLog() { @echo "cat ${SERVICE_LOG_FILE}:" echo "--------------------------------------------------------------------------------" cat "${SERVICE_LOG_FILE}" echo "--------------------------------------------------------------------------------" echo "^^^^^ ${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} ) ;; log) @serviceLog ;; tail) @serviceTail ;; *) @echo "Actions: [setup|start|start-read-wait|ready-wait|stop|restart|status|run|tail]" exit 1 ;; esac