Permalink
Switch branches/tags
Nothing to show
Find file Copy path
executable file 199 lines (170 sloc) 5.23 KB
#!/bin/bash
# Simple CI
set -o pipefail
set -o nounset
# Defaults
FORCE=0
VERBOSE=0
REPO=""
EMAIL=""
NAME="ci"
TEST_DIR="."
TEST_COMMAND="./test.sh"
MAIL_CMD="mail"
MAIL_CMD_ATTACH_FLAG="-A"
MAIL_CMD_RECIPIENTS_FLAG="-t"
MAIL_CMD_SUBJECT_FLAG="-s"
PRE_SCRIPT="/bin/true"
POST_SCRIPT="/bin/true"
TIMEOUT_S=86400
PAGER=${PAGER:-more}
function show_help() {
cat > /dev/stdout << END
${0} -r <repo> -l <local_checkout> [-q <pre-script>] [-w <post-script>]
[-m <email>] [-a <mail command>] [-t <mail command attach flag>]
[-s <mail command subject flag] [-e <recipients flag>] [-n name] [-d <dir>]
[-c <command>] [-f] [-v] [-h]
REQUIRED ARGS:
-r - git repository, eg https://github.com/myname/myproj (required)
-l - local checkout of code (that gets updated to determine whether a run is needed) (required)
OPTIONAL ARGS:
-q - script to run just before actually performing test (default ${PRE_SCRIPT})
-w - script to run just after actually performing test (default ${POST_SCRIPT})
-m - email address to send to using "mail" command (default logs to stdout)
-a - mail command to use (default=${MAIL_CMD})
-n - name for ci (unique, must be a valid directory name), eg myproj (default=${NAME})
-d - directory within repository to navigate to (default=${TEST_DIR})
-c - test command to run from -d directory (default=${TEST_COMMAND})
-t - attach argument flag for mail command (default=${MAIL_CMD_ATTACH_FLAG}, empty string means no-attach)
-s - subject flag for mail command (default=${MAIL_CMD_RECIPIENTS_FLAG})
-e - recipients flag (default=${MAIL_CMD_RECIPIENTS_FLAG}, empty string means no flag needed)
-f - force a run even if repo has no updates (default off)
-v - verbose logging (default off)
-i - timeout in seconds (default 86400, ie one day, does KILL one hour after that)
-h - show help
EXAMPLES
- "Clone -r https://github.com/ianmiell/shutit.git if a git pull on /space/git/shutit indicates there's been an update.
Then navigate to test, run ./test.sh and mail ian.miell@gmail.com if there are any issues"
./cheapci -r https://github.com/ianmiell/shutit.git -l /space/git/shutit -d test -c ./test.sh -m ian.miell@gmail.com
- "Run this continuously in a crontab."
Crontab line:
* * * * * cd /path/to/cheapci && ./cheapci -r https://github.com/ianmiell/shutit.git -l /space/git/shutit -d test -c ./test.sh -m ian.miell@gmail.com
END
}
while getopts "h?vfm:n:d:r:l:c:a:q:w:t:e:s:" opt
do
case "${opt}" in
h|\?)
show_help
exit 0
;;
v) VERBOSE=1 ;;
f) FORCE=1 ;;
r) REPO=${OPTARG} ;;
m) EMAIL=${OPTARG} ;;
n) NAME=${OPTARG} ;;
d) TEST_DIR=${OPTARG} ;;
l) LOCAL_CHECKOUT=${OPTARG} ;;
c) TEST_COMMAND=${OPTARG} ;;
q) PRE_SCRIPT=${OPTARG} ;;
w) POST_SCRIPT=${OPTARG} ;;
a) MAIL_CMD=${OPTARG} ;;
t) MAIL_CMD_ATTACH_FLAG=${OPTARG} ;;
e) MAIL_CMD_RECIPIENTS_FLAG=${OPTARG} ;;
s) MAIL_CMD_SUBJECT_FLAG=${OPTARG} ;;
i) TIMEOUT_S=${OPTARG} ;;
esac
done
shift "$((OPTIND-1))"
if [[ ${REPO} = "" ]]
then
show_help
exit 1
fi
# To force a run even if no updates.
if [[ ${VERBOSE} -gt 0 ]]
then
set -x
fi
BUILD_DIR_BASE="/tmp/${NAME}"
BUILD_DIR="${BUILD_DIR_BASE}/${NAME}_builddir"
mkdir -p "${BUILD_DIR}"
LOG_FILE="${BUILD_DIR}/${NAME}_build_${RANDOM}.log.txt"
BUILD_LOG_FILE="${BUILD_DIR}/${NAME}_build.log.txt"
LOCK_FILE="${BUILD_DIR}/${NAME}_ci.lck"
function cleanup() {
rm -rf "${BUILD_DIR}"
rm -f "${LOCK_FILE}"
# get rid of /tmp detritus, leaving anything accessed 2 days ago+
find "${BUILD_DIR_BASE}"/* -type d -atime +1 -exec rm {} -rf +
echo "cleanup done"
}
function send_mail() {
msg=${1}
if [[ ${LOG_FILE} != "" ]] && [[ ${MAIL_CMD_ATTACH_FLAG} != "" ]]
then
log_file_arg=(${MAIL_CMD_ATTACH_FLAG} ${LOG_FILE})
fi
if [[ ${EMAIL} != "" ]] && [[ ${MAIL_CMD} != "" ]]
then
echo "${msg}" | ${MAIL_CMD} "${MAIL_CMD_SUBJECT_FLAG}" "${msg}" "${log_file_arg[@]}" "${MAIL_CMD_RECIPIENTS_FLAG}" "${EMAIL}"
else
echo "${msg}"
fi
}
date 2>&1 | tee -a "${BUILD_LOG_FILE}"
# Lockfile
if [[ -a ${LOCK_FILE} ]]
then
echo "Already running" | tee -a "${BUILD_LOG_FILE}"
exit
fi
trap cleanup TERM INT QUIT EXIT
touch "${LOCK_FILE}"
# Fetch changes
pushd "${LOCAL_CHECKOUT}"
git fetch origin master 2>&1 | tee -a "${BUILD_LOG_FILE}"
# See if there are any incoming changes
updates=$(git log HEAD..origin/master --oneline | wc -l)
echo "Updates: ${updates}" | tee -a "${BUILD_LOG_FILE}"
if [[ ${updates} -gt 0 ]] || [[ ${FORCE} -gt 0 ]]
then
touch "${LOG_FILE}"
pushd "${LOCAL_CHECKOUT}"
echo "Pulling" | tee -a "${LOG_FILE}"
git pull origin master 2>&1 | tee -a "${LOG_FILE}"
popd
# This won't exist in a bit so no point pushd'ing
pushd "${BUILD_DIR}"
# Clone to NAME
git clone "${REPO}" "${NAME}"
popd
${PRE_SCRIPT} 2>&1 | tee -a "${LOG_FILE}"
EXIT_CODE="${?}"
if [[ ${EXIT_CODE} -ne 0 ]]
then
msg="ANGRY ${NAME} on $(hostname)"
fi
pushd "${BUILD_DIR}"/"${NAME}"/"${TEST_DIR}"
timeout "${TIMEOUT_S}" "${TEST_COMMAND}" 2>&1 | tee -a "${LOG_FILE}"
EXIT_CODE=$?
popd
if [[ ${EXIT_CODE} -ne 0 ]]
then
if [[ ${EXIT_CODE} -eq 124 ]]
then
msg="ANGRY (TIMEOUT) ${NAME} on $(hostname)"
else
msg="ANGRY ${NAME} on $(hostname)"
fi
else
msg="HAPPY ${NAME} on $(hostname)"
fi
${POST_SCRIPT} 2>&1 | tee -a "${LOG_FILE}"
EXIT_CODE=$?
if [[ ${EXIT_CODE} -ne 0 ]]
then
msg="ANGRY ${NAME} on $(hostname)"
fi
send_mail "${msg}"
fi