#!/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= name of configuration file, default to ${DEFAULT_CONFIG_FILE} -e, --errs= name of errors file, default to ${DEFAULT_ERRORS_FILE} -H, --hash= repo hash to checkout -n, --name= repo name to clone -t, --test just test config file and exit -T, --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 "$@"