netoik-cicd/src/pipeline.sh

254 lines
6.8 KiB
Bash
Executable File

#!/bin/bash
################################################################################
# #
# netoik-cicd-pipeline #
# #
# This binary is made to be run by git server, it asks runner server to check #
# code and eventually to deploy rpm package. #
# #
# This script is coded with respect of google shell styleguide: #
# https://google.github.io/styleguide/shellguide.html #
# #
################################################################################
# Exit immediately if any command fails.
set -e
# Exit with the last non-zero fail code.
set -o pipefail
# Program constants.
readonly PACKAGE_NAME="netoik-cicd"
readonly PROGRAM_NAME="${PACKAGE_NAME}-pipeline"
# Declare default variables.
readonly DEFAULT_CONFIG_FILE="/etc/${PACKAGE_NAME}/${PACKAGE_NAME}.conf"
readonly DEFAULT_ERRORS_FILE="/etc/${PACKAGE_NAME}/errors.conf"
fail() (
msg="$1"
if [ $# -eq 2 ]; then
code="$2"
else
code=1
fi
echo "${msg}" >&2
if [ "${code}" -gt 0 ]; then
exit "${code}"
else
echo "Undefined error code for '${msg}'." >&2
exit 1
fi
)
usage() (
echo "Usage: ${PROGRAM_NAME} [OPTION]... PIPELINE
Asks runner server to check code and eventually deploy rpm package. See section Pipelines for positionnal argument PIPELINE.
Mandatory argumentes for long options are mandatory for short options too.
-c, --conf=<config_file> name of configuration file, default
to ${DEFAULT_CONFIG_FILE}
-e, --errs=<errors_file> name of errors file, default to
${DEFAULT_ERRORS_FILE}
-H, --hash=<repo_hash> repo hash to checkout
-n, --name=<repo_name> repo name to clone
-t, --test just test config file and exit
-T, --tag=<repo_tag> repo tag to deploy
-h, --help display this help message and exit
Pipelines:
newcommit
should be called when a new commit is pushed to repo, it will clone repo and
check code validity
it needs options -H|--hash, -n|--name
newtag
should be called when a new tag is pushed to repo, it will clone repo,
check code validity and deploy rpm package
it needs options -H|--hash, -n|--name, -T|--tag
"
)
main() (
# Parse arguments.
testing="false"
config_file="${DEFAULT_CONFIG_FILE}"
errors_file="${DEFAULT_ERRORS_FILE}"
if ! args="$(getopt --name "${PROGRAM_NAME}" \
--options c:e:H:hn:tT: \
--longoptions conf:,errs:,hash:,help,name:,test,tag:,help \
-- "$@")"; then
usage
fail "Bad arguments."
fi
eval set -- "${args}"
while true; do
case "$1" in
-c | --conf)
config_file="$2"
shift 2
;;
-e | --errs)
errors_file="$2"
shift 2
;;
-H | --hash)
repo_hash="$2"
shift 2
;;
-h | --help)
usage
exit 0
;;
-n | --name)
repo_name="$2"
shift 2
;;
-t | --test)
testing="true"
shift
;;
-T | --tag)
repo_tag="$2"
shift 2
;;
--)
shift
break
;;
*)
usage
fail "Unexpected option '$1'."
;;
esac
done
if [ $# -ne 1 ]; then
usage
fail "Missing positionnal argument PIPELINE."
fi
pipeline="$1"
# Generate pipeline tmp sock.
tsp="$(date +%s)"
random="$(echo "${RANDOM}" | md5sum | head --bytes 32)"
rd_pipeline_sock_dir="${pipeline_sock_dir}/${pipeline}-${tsp}-${random}"
rd_pipeline_sock="${pipeline_sock_dir}/pipeline.sock"
# Check pipeline with options.
case "${pipeline}" in
newcommit)
if [ -z "${repo_name}" ]; then
usage
fail "Missing option -n|--name for pipeline '${pipeline}'."
fi
if [ -z "${repo_hash}" ]; then
usage
fail "Missing option -H|--hash for pipeline '${pipeline}'."
fi
runner_request="$(jq --null-input --compact-output \
--arg s "${rd_pipeline_sock}" \
--arg n "${repo_name}" \
--arg h "${repo_hash}" \
'{"response_sock":$s,"repo_name":$n,"repo_hash":$h}')"
;;
newtag)
if [ -z "${repo_name}" ]; then
usage
fail "Missing option -n|--name for pipeline '${pipeline}'."
fi
if [ -z "${repo_hash}" ]; then
usage
fail "Missing option -H|--hash for pipeline '${pipeline}'."
fi
if [ -z "${repo_tag}" ]; then
usage
fail "Missing option -T|--tag for pipeline '${pipeline}'."
fi
runner_request="$(jq --null-input --compact-output \
--arg s "${rd_pipeline_sock}" \
--arg n "${repo_name}" \
--arg h "${repo_hash}" \
--arg t "${repo_tag}" \
'{"response_sock":$s,"repo_name":$n,"repo_hash":$h,"repo_tag":$t}')"
;;
*)
usage
fail "Invalid pipeline '$1'."
;;
esac
# Load config file.
if [ ! -r "${config_file}" ]; then
fail "Config file '${config_file}' is not readable."
fi
# shellcheck source=./conf/netoik-cicd.conf.sample
source "${config_file}"
# Load errors file.
if [ ! -r "${errors_file}" ]; then
fail "Errors file '${errors_file}' is not reachable."
fi
# shellcheck source=./conf/errors.conf.sample
source "${errors_file}"
# Check variables in config file.
if [ -z "${runner_sock}" ]; then
fail "Variable runner_sock is empty." "${err_runner_sock_empty}"
fi
if [ ! -S "${runner_sock}" ]; then
fail "Sock runner_sock='${runner_sock}' does not exist." \
"${err_runner_sock_not_exist}"
fi
if [ -z "${runner_timeout}" ]; then
fail "Variable runner_timeout is empty." "${err_runner_timeout_empty}"
fi
if [ "${runner_timeout}" -lt 0 ]; then
fail "Runner timeout is not valid: ${runner_timeout}." \
"${err_runner_timeout_not_valid}"
fi
if [ -z "${git_runner_groupname}" ]; then
fail "Variable git_runner_groupname is empty." "${err_git_runner_groupname_empty}"
fi
if ! getent group "${git_runner_groupname}"; then
fail "Git-runner group '${git_runner_groupname}' does not exist." \
"${err_git_runner_group_not_exist}"
fi
# Stop now if testing.
if "${testing}"; then
exit 0
fi
# Send request to runner.
mkdir "${rd_pipeline_sock_dir}"
(
inotifywait --quiet --quiet --event create "${rd_pipeline_sock_dir}"
chmod 775 "${rd_pipeline_sock}"
chgrp "${git_runner_groupname}" "${rd_pipeline_sock}"
echo "${runner_request}" | ncat --unixsock "${runner_sock}"
) &
# Wait for runner response.
if [ "${runner_timeout}" -gt 0 ]; then
response="$(timeout "${runner_timeout}" ncat --listen \
--unixsock "${rd_pipeline_sock}")"
else
response="$(ncat --listen --unixsock "${rd_pipeline_sock}")"
fi
# Remove random directory.
rm --recursive "${rd_pipeline_sock_dir}"
# Display response.
echo -e "$(echo "${response}" | jq .msg)"
exit "$(echo "${response}" | jq .code)"
)
main "$@"