Improved Bash Logger (#1246)

* Improved Bash Logger

This is a first step towards having robust Bash scripts in the runner images. The changes _could_ be considered breaking, depending on our backwards compatibility definition.

* Fixed Log Formatting Issues

Co-authored-by: Callum Tait <15716903+toast-gear@users.noreply.github.com>
This commit is contained in:
Richard Fussenegger 2022-04-12 23:02:06 +02:00 committed by GitHub
parent 7b8057e417
commit 8db071c4ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 121 additions and 83 deletions

View File

@ -110,7 +110,9 @@ RUN mkdir /opt/hostedtoolcache \
&& chgrp docker /opt/hostedtoolcache \ && chgrp docker /opt/hostedtoolcache \
&& chmod g+rwx /opt/hostedtoolcache && chmod g+rwx /opt/hostedtoolcache
COPY entrypoint.sh / # We place the scripts in `/usr/bin` so that users who extend this image can
# override them with scripts of the same name placed in `/usr/local/bin`.
COPY entrypoint.sh logger.bash /usr/bin/
# Add the Python "User Script Directory" to the PATH # Add the Python "User Script Directory" to the PATH
ENV PATH="${PATH}:${HOME}/.local/bin" ENV PATH="${PATH}:${HOME}/.local/bin"
@ -122,4 +124,4 @@ RUN echo "PATH=${PATH}" > /etc/environment \
USER runner USER runner
ENTRYPOINT ["/usr/local/bin/dumb-init", "--"] ENTRYPOINT ["/usr/local/bin/dumb-init", "--"]
CMD ["/entrypoint.sh"] CMD ["entrypoint.sh"]

View File

@ -97,13 +97,11 @@ ENV RUNNER_TOOL_CACHE=/opt/hostedtoolcache
RUN mkdir /opt/hostedtoolcache \ RUN mkdir /opt/hostedtoolcache \
&& chgrp docker /opt/hostedtoolcache \ && chgrp docker /opt/hostedtoolcache \
&& chmod g+rwx /opt/hostedtoolcache && chmod g+rwx /opt/hostedtoolcache
COPY modprobe startup.sh /usr/local/bin/
COPY supervisor/ /etc/supervisor/conf.d/
COPY logger.sh /opt/bash-utils/logger.sh
COPY entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/startup.sh /usr/local/bin/entrypoint.sh /usr/local/bin/modprobe # We place the scripts in `/usr/bin` so that users who extend this image can
# override them with scripts of the same name placed in `/usr/local/bin`.
COPY modprobe entrypoint.sh startup.sh logger.bash /usr/bin/
COPY supervisor/ /etc/supervisor/conf.d/
# arch command on OS X reports "i386" for Intel CPUs regardless of bitness # arch command on OS X reports "i386" for Intel CPUs regardless of bitness
RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \ RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \

View File

@ -1,51 +1,35 @@
#!/bin/bash #!/bin/bash
source logger.bash
RUNNER_ASSETS_DIR=${RUNNER_ASSETS_DIR:-/runnertmp} RUNNER_ASSETS_DIR=${RUNNER_ASSETS_DIR:-/runnertmp}
RUNNER_HOME=${RUNNER_HOME:-/runner} RUNNER_HOME=${RUNNER_HOME:-/runner}
LIGHTGREEN="\e[0;32m"
LIGHTRED="\e[0;31m"
WHITE="\e[0;97m"
RESET="\e[0m"
log(){
printf "${WHITE}${@}${RESET}\n" 1>&2
}
success(){
printf "${LIGHTGREEN}${@}${RESET}\n" 1>&2
}
error(){
printf "${LIGHTRED}${@}${RESET}\n" 1>&2
}
if [ ! -z "${STARTUP_DELAY_IN_SECONDS}" ]; then if [ ! -z "${STARTUP_DELAY_IN_SECONDS}" ]; then
log "Delaying startup by ${STARTUP_DELAY_IN_SECONDS} seconds" log.notice "Delaying startup by ${STARTUP_DELAY_IN_SECONDS} seconds"
sleep ${STARTUP_DELAY_IN_SECONDS} sleep ${STARTUP_DELAY_IN_SECONDS}
fi fi
if [[ "${DISABLE_WAIT_FOR_DOCKER}" != "true" ]] && [[ "${DOCKER_ENABLED}" == "true" ]]; then if [[ "${DISABLE_WAIT_FOR_DOCKER}" != "true" ]] && [[ "${DOCKER_ENABLED}" == "true" ]]; then
log "Docker enabled runner detected and Docker daemon wait is enabled" log.debug 'Docker enabled runner detected and Docker daemon wait is enabled'
log "Waiting until Docker is available or the timeout is reached" log.debug 'Waiting until Docker is available or the timeout is reached'
timeout 120s bash -c 'until docker ps ;do sleep 1; done' timeout 120s bash -c 'until docker ps ;do sleep 1; done'
else else
log "Docker wait check skipped. Either Docker is disabled or the wait is disabled, continuing with entrypoint" log.notice 'Docker wait check skipped. Either Docker is disabled or the wait is disabled, continuing with entrypoint'
fi fi
if [ -z "${GITHUB_URL}" ]; then if [ -z "${GITHUB_URL}" ]; then
log "Working with public GitHub" log.debug 'Working with public GitHub'
GITHUB_URL="https://github.com/" GITHUB_URL="https://github.com/"
else else
length=${#GITHUB_URL} length=${#GITHUB_URL}
last_char=${GITHUB_URL:length-1:1} last_char=${GITHUB_URL:length-1:1}
[[ $last_char != "/" ]] && GITHUB_URL="$GITHUB_URL/"; : [[ $last_char != "/" ]] && GITHUB_URL="$GITHUB_URL/"; :
log "Github endpoint URL ${GITHUB_URL}" log.debug "Github endpoint URL ${GITHUB_URL}"
fi fi
if [ -z "${RUNNER_NAME}" ]; then if [ -z "${RUNNER_NAME}" ]; then
error "RUNNER_NAME must be set" log.error 'RUNNER_NAME must be set'
exit 1 exit 1
fi fi
@ -58,12 +42,12 @@ elif [ -n "${RUNNER_REPO}" ]; then
elif [ -n "${RUNNER_ENTERPRISE}" ]; then elif [ -n "${RUNNER_ENTERPRISE}" ]; then
ATTACH="enterprises/${RUNNER_ENTERPRISE}" ATTACH="enterprises/${RUNNER_ENTERPRISE}"
else else
error "At least one of RUNNER_ORG or RUNNER_REPO or RUNNER_ENTERPRISE must be set" log.error 'At least one of RUNNER_ORG, RUNNER_REPO, or RUNNER_ENTERPRISE must be set'
exit 1 exit 1
fi fi
if [ -z "${RUNNER_TOKEN}" ]; then if [ -z "${RUNNER_TOKEN}" ]; then
error "RUNNER_TOKEN must be set" log.error 'RUNNER_TOKEN must be set'
exit 1 exit 1
fi fi
@ -73,7 +57,7 @@ fi
# Hack due to https://github.com/actions-runner-controller/actions-runner-controller/issues/252#issuecomment-758338483 # Hack due to https://github.com/actions-runner-controller/actions-runner-controller/issues/252#issuecomment-758338483
if [ ! -d "${RUNNER_HOME}" ]; then if [ ! -d "${RUNNER_HOME}" ]; then
error "${RUNNER_HOME} should be an emptyDir mount. Please fix the pod spec." log.error "$RUNNER_HOME should be an emptyDir mount. Please fix the pod spec."
exit 1 exit 1
fi fi
@ -94,16 +78,16 @@ cd ${RUNNER_HOME}
config_args=() config_args=()
if [ "${RUNNER_FEATURE_FLAG_EPHEMERAL:-}" == "true" -a "${RUNNER_EPHEMERAL}" == "true" ]; then if [ "${RUNNER_FEATURE_FLAG_EPHEMERAL:-}" == "true" -a "${RUNNER_EPHEMERAL}" == "true" ]; then
config_args+=(--ephemeral) config_args+=(--ephemeral)
echo "Passing --ephemeral to config.sh to enable the ephemeral runner." log.debug 'Passing --ephemeral to config.sh to enable the ephemeral runner.'
fi fi
if [ "${DISABLE_RUNNER_UPDATE:-}" == "true" ]; then if [ "${DISABLE_RUNNER_UPDATE:-}" == "true" ]; then
config_args+=(--disableupdate) config_args+=(--disableupdate)
echo "Passing --disableupdate to config.sh to disable automatic runner updates." log.debug 'Passing --disableupdate to config.sh to disable automatic runner updates.'
fi fi
retries_left=10 retries_left=10
while [[ ${retries_left} -gt 0 ]]; do while [[ ${retries_left} -gt 0 ]]; do
log "Configuring the runner." log.debug 'Configuring the runner.'
./config.sh --unattended --replace \ ./config.sh --unattended --replace \
--name "${RUNNER_NAME}" \ --name "${RUNNER_NAME}" \
--url "${GITHUB_URL}${ATTACH}" \ --url "${GITHUB_URL}${ATTACH}" \
@ -113,18 +97,18 @@ while [[ ${retries_left} -gt 0 ]]; do
--work "${RUNNER_WORKDIR}" "${config_args[@]}" --work "${RUNNER_WORKDIR}" "${config_args[@]}"
if [ -f .runner ]; then if [ -f .runner ]; then
success "Runner successfully configured." log.debug 'Runner successfully configured.'
break break
fi fi
error "Configuration failed. Retrying" log.debug 'Configuration failed. Retrying'
retries_left=$((retries_left - 1)) retries_left=$((retries_left - 1))
sleep 1 sleep 1
done done
if [ ! -f .runner ]; then if [ ! -f .runner ]; then
# we couldn't configure and register the runner; no point continuing # we couldn't configure and register the runner; no point continuing
error "Configuration failed!" log.error 'Configuration failed!'
exit 2 exit 2
fi fi
@ -159,8 +143,10 @@ fi
args=() args=()
if [ "${RUNNER_FEATURE_FLAG_EPHEMERAL:-}" != "true" -a "${RUNNER_EPHEMERAL}" == "true" ]; then if [ "${RUNNER_FEATURE_FLAG_EPHEMERAL:-}" != "true" -a "${RUNNER_EPHEMERAL}" == "true" ]; then
args+=(--once) args+=(--once)
echo "[WARNING] Passing --once is deprecated and will be removed as an option from the image and ARC at the release of 0.24.0." log.warning 'Passing --once is deprecated and will be removed as an option' \
echo "[WARNING] Upgrade to GHES => 3.3 to continue using actions-runner-controller. If you are using github.com ignore this warning." 'from the image and actions-runner-controller at the release of 0.24.0.' \
'Upgrade to GHES => 3.3 to continue using actions-runner-controller. If' \
'you are using github.com ignore this warning.'
fi fi
# Unset entrypoint environment variables so they don't leak into the runner environment # Unset entrypoint environment variables so they don't leak into the runner environment

73
runner/logger.bash Executable file
View File

@ -0,0 +1,73 @@
#!/usr/bin/env bash
# We are not using `set -Eeuo pipefail` here because this file is sourced by
# other scripts that might not be ready for a strict Bash setup. The functions
# in this file do not require it, because they are not handling signals, have
# no external calls that can fail (printf as well as date failures are ignored),
# are not using any variables that need to be set, and are not using any pipes.
# This logger implementation can be replaced with another logger implementation
# by placing a script called `logger.bash` in `/usr/local/bin` of the image. The
# only requirement for the script is that it defines the following functions:
#
# - `log.debug`
# - `log.notice`
# - `log.warning`
# - `log.error`
# - `log.success`
#
# Each function **MUST** accept an arbitrary amount of arguments that make up
# the (unstructured) logging message.
#
# Additionally the following environment variables **SHOULD** be supported to
# disable their corresponding log entries, the value of the variables **MUST**
# not matter the mere fact that they are set is all that matters:
#
# - `LOG_DEBUG_DISABLED`
# - `LOG_NOTICE_DISABLED`
# - `LOG_WARNING_DISABLED`
# - `LOG_ERROR_DISABLED`
# - `LOG_SUCCESS_DISABLED`
# The log format is constructed in a way that it can easily be parsed with
# standard tools and simple string manipulations; pattern and example:
#
# YYYY-MM-DD hh:mm:ss.SSS $level --- $message
# 2022-03-19 10:01:23.172 NOTICE --- example message
#
# This function is an implementation detail and **MUST NOT** be called from
# outside this script (which is possible if the file is sourced).
__log() {
local color instant level
color=${1:?missing required <color> argument}
shift
level=${FUNCNAME[1]} # `main` if called from top-level
level=${level#log.} # substring after `log.`
level=${level^^} # UPPERCASE
if [[ ! -v "LOG_${level}_DISABLED" ]]; then
instant=$(date '+%F %T.%-3N' 2>/dev/null || :)
# https://no-color.org/
if [[ -v NO_COLOR ]]; then
printf -- '%s %s --- %s\n' "$instant" "$level" "$*" 1>&2 || :
else
printf -- '\033[0;%dm%s %s --- %s\033[0m\n' "$color" "$instant" "$level" "$*" 1>&2 || :
fi
fi
}
# To log with a dynamic level use standard Bash capabilities:
#
# level=notice
# command || level=error
# "log.$level" message
#
# @formatter:off
log.debug () { __log 37 "$@"; } # white
log.notice () { __log 34 "$@"; } # blue
log.warning () { __log 33 "$@"; } # yellow
log.error () { __log 31 "$@"; } # red
log.success () { __log 32 "$@"; } # green
# @formatter:on

View File

@ -1,24 +0,0 @@
#!/bin/sh
# Logger from this post http://www.cubicrace.com/2016/03/log-tracing-mechnism-for-shell-scripts.html
function INFO(){
local function_name="${FUNCNAME[1]}"
local msg="$1"
timeAndDate=`date`
echo "[$timeAndDate] [INFO] [${0}] $msg"
}
function DEBUG(){
local function_name="${FUNCNAME[1]}"
local msg="$1"
timeAndDate=`date`
echo "[$timeAndDate] [DEBUG] [${0}] $msg"
}
function ERROR(){
local function_name="${FUNCNAME[1]}"
local msg="$1"
timeAndDate=`date`
echo "[$timeAndDate] [ERROR] $msg"
}

0
runner/modprobe Normal file → Executable file
View File

35
runner/startup.sh Normal file → Executable file
View File

@ -1,13 +1,13 @@
#!/bin/bash #!/bin/bash
source /opt/bash-utils/logger.sh source logger.bash
function wait_for_process () { function wait_for_process () {
local max_time_wait=30 local max_time_wait=30
local process_name="$1" local process_name="$1"
local waited_sec=0 local waited_sec=0
while ! pgrep "$process_name" >/dev/null && ((waited_sec < max_time_wait)); do while ! pgrep "$process_name" >/dev/null && ((waited_sec < max_time_wait)); do
INFO "Process $process_name is not running yet. Retrying in 1 seconds" log.debug "Process $process_name is not running yet. Retrying in 1 seconds"
INFO "Waited $waited_sec seconds of $max_time_wait seconds" log.debug "Waited $waited_sec seconds of $max_time_wait seconds"
sleep 1 sleep 1
((waited_sec=waited_sec+1)) ((waited_sec=waited_sec+1))
if ((waited_sec >= max_time_wait)); then if ((waited_sec >= max_time_wait)); then
@ -33,29 +33,32 @@ jq ".\"registry-mirrors\"[0] = \"${DOCKER_REGISTRY_MIRROR}\"" /etc/docker/daemon
fi fi
SCRIPT SCRIPT
INFO "Using /etc/docker/daemon.json with the following content" dump() {
local path=${1:?missing required <path> argument}
shift
printf -- "%s\n---\n" "${*//\{path\}/"$path"}" 1>&2
cat "$path" 1>&2
printf -- '---\n' 1>&2
}
cat /etc/docker/daemon.json for config in /etc/docker/daemon.json /etc/supervisor/conf.d/dockerd.conf; do
dump "$config" 'Using {path} with the following content:'
done
INFO "Using /etc/supervisor/conf.d/dockerd.conf with the following content" log.debug 'Starting supervisor daemon'
cat /etc/supervisor/conf.d/dockerd.conf
INFO "Starting supervisor"
sudo /usr/bin/supervisord -n >> /dev/null 2>&1 & sudo /usr/bin/supervisord -n >> /dev/null 2>&1 &
INFO "Waiting for processes to be running" log.debug 'Waiting for processes to be running...'
processes=(dockerd) processes=(dockerd)
for process in "${processes[@]}"; do for process in "${processes[@]}"; do
wait_for_process "$process" wait_for_process "$process"
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
ERROR "$process is not running after max time" log.error "$process is not running after max time"
ERROR "Dumping /var/log/dockerd.err.log to help investigation" dump /var/log/dockerd.err.log 'Dumping {path} to aid investigation'
cat /var/log/dockerd.err.log
exit 1 exit 1
else else
INFO "$process is running" log.debug "$process is running"
fi fi
done done