#! /bin/bash
# Generate SRPM against Copr's dist-git instance.
# Copyright (C) 2017 Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#################################
##### BACKEND / BUILDER API #####
#################################
# PID file of the main process is stored here. Backend can read this file to
# detect the last PID of copr-builder run.
opt_pidfile=/var/lib/copr-builder/pid
# Build results are stored here.
opt_resultdir=/var/lib/copr-builder/results
# This is to be read by 'tail -n --retry +0 -f --pid=...' on backend side.
opt_log_file=/var/lib/copr-builder/live-log
#################################
#################################
opt_copr=
opt_package=
opt_revision=
opt_chroot=fedora-rawhide-x86_64
opt_config=/etc/copr-builder/fedora-copr.conf
opt_lockfile=/var/lib/copr-builder/lock
opt_workdir=
opt_timeout=
opt_workdir_cleanup=false
opt_download_mock_config=:
opt_debug=false
opt_host_resolv=
opt_detached=false
error() { echo >&2 " ! $*"; }
die() { error "$*"; exit 1; }
info() { echo 2>&1 " * $*"; }
debug() { if $opt_debug; then echo 2>&1 " ~ $*"; fi ; }
destruct ()
{
debug "running script cleanup"
if $opt_workdir_cleanup && test -n "$opt_workdir" && test -d "$opt_workdir"
then
debug "cleaning workdir"
rm -rf "$opt_workdir"
fi
if test -n "$opt_lockfile" && test -f "$opt_lockfile"
then
rm -rf "$opt_lockfile"
fi
}
quote_args ()
{
quote_args_result=
local sp=
for arg
do
quote_args_result+=$sp$(printf %q "$arg")
sp=' '
done
}
filter_backpaces ()
{
# Drop terminal sequences and strings terminated by CR (not CR LF)
sed -e 's|\x1B\[[0-9;]*[a-zA-Z]||g' \
-e 's/.*\x0D\([^\x0a]\)/\1/g' --unbuffered
}
stdout_wrap ()
(
set -o pipefail
# Normally, to have both stdout and stderr cleaned from backspaces, we would
# do '"$@" 2> >(filter_backpaces >&2) | filter_backpaces -b'; IOW attaching
# fifo to stderr and piping stdout through col -b. But because we want to
# wrap some 'mock' calls too, and mock has the ugly hack with
# 'stderr.isatty()' (otherwise the output is very quiet, rhbz#1166609), we
# need to run through 'unbuffer' to fake the terminal.
unbuffer "$@" 2> >(filter_backpaces >&2) | filter_backpaces
)
show_help()
{
cat <<EOHELP >&2
Usage: $0 OPTION
Build package against copr from copr dist-git.
Options:
--copr copr; <user>/<project> or @<group>/<project>)
--package component (package name) in particular copr
--revision git commit reference in dist-git; git hash, tag,
branch..
--chroot chroot, e.g. fedora-rawhide-x86_64
--config configuration file
--resultdir directory to place build results
--workdir by default, temporary working directory under
/var/lib/copr-builder is used
--define="ARG VALUE" define rpm macro
--host-resolv=[0|1] use host's resolv conf
--detached fork the builder and run in background, this
always succeeds and prints PID of the child to
standard output
--log-file=LOG store stdout/stderr into LOG
--timeout=SECONDS fail miserably after SECONDS timeout
--mock-opts=OPTS additional mock options, shell-quote OPTS properly
EOHELP
test -n "$1" && exit "$1"
}
long_options=copr:,package:,revision:,config:,resultdir:,workdir:,define:
long_options+=,host-resolv:,detached,log-file:,timeout:,chroot:,mock-opts:
oldargs=( "$@" )
ARGS=$(getopt -o "h" -l "$long_options,help" -n "getopt" -- "$@")
mock=(mock)
eval set -- "$ARGS"
while :; do
case "$1" in
--copr|--package|--revision|--config|--resultdir|--workdir| \
--host-resolv|--log-file|--timeout|--chroot)
opt=${1##--}
opt=${opt##-}
opt=${opt//-/_}
eval "opt_$opt=\$2"
shift 2
;;
--detached)
opt=${1##--}
opt=${opt##-}
opt=${opt//-/_}
eval "opt_$opt=:"
shift 1
;;
--mock-opts)
# Eval is needed to keep the right number of arguments, e.g.
# --mock-opts='--rpmbuild-opts "-vv --fsmdebug -ddd --rpmiodebug"'
eval "mock+=( $2 )"
shift 2
;;
--define)
mock+=(--define "$2")
shift 2
;;
--help)
show_help 0
;;
--) # end!
shift
break
;;
*)
echo "programmer mistake ($1)" >&2
exit 1
;;
esac
done
if $opt_detached; then
# Parent process. Re-exec ourselves in background, but now _without_
# the --detached option.
new_args=()
for arg in "${oldargs[@]}"
do
case $arg in
--detached) ;;
*) new_args+=( "$arg" ) ;;
esac
done
# Copr backend runs:
# 'ssh -t <opts> "copr-builder --detached"'
# This triggers those processes on builder-side:
# (0) + bash -c 'copr-builder --detached'
# (1) + copr-builder --detached
# (2) + copr-builder ... (detached)
# The pseudo terminal allocated for this session is closed right after the
# 'exit 0' command below, which means SIGHUP is sent to (2) command.
# Ignoring the SIGHUP in (2) would be racy (signal can be delivered before
# the signal handler is actually installed) so we rather ignore it here
# already in process (1).
trap "" SIGHUP
"$0" "${new_args[@]}" &>/dev/null &
echo $!
exit 0
fi
# Reexec ourselves if we want to timeout.
test -n "$opt_timeout" && test -z "$TIMEOUT_SET" \
&& TIMEOUT_SET=: exec timeout "$opt_timeout" "$0" "${oldargs[@]}"
# Start doing the work, but ensure only one copr-builder at the same time!
set -e
exec 9>"$opt_lockfile"
flock -n 9 || die "can't lock $opt_lockfile"
# Ensure there are no leftovers.
trap destruct EXIT
# Make the "re-attaching" in copr-backend Worker convenient. That's to be done
# by 'tail --retry -f --pid=`cat pidfile`'.
echo $$ > "$opt_pidfile"
# Duplicate all output to the log file.
exec &> >(tee -i "$opt_log_file")
quote_args copr-builder "${oldargs[@]}"
info "running command: $quote_args_result"
opt_parse_error ()
{
error "$*"
opt_parse_success=false
}
opt_parse_success=:
for opt in copr package revision config chroot; do
eval "test -z \"\$opt_$opt\"" \
&& opt_parse_error "missing argument --$opt"
done
test -r "$opt_config" \
|| opt_parse_error "Missing or unreadable config file '$opt_config'"
opt_config=$(readlink -f "$opt_config")
case $opt_resultdir in
:tmp:)
opt_resultdir=$(mktemp -d)
info "Results to be placed into '$opt_resultdir'"
;;
/var/lib/copr-builder/results)
# We know that it is safe to remove everything from here.
rm -rf "$opt_resultdir"
mkdir "$opt_resultdir"
;;
esac
test -d "$opt_resultdir" || opt_parse_error "'$opt_resultdir' is not dir"
if test -n "$opt_workdir"; then
if test -d "$opt_workdir"; then
test -w "$opt_workdir" || opt_parse_error "'$opt_workdir' is not a dir"
else
opt_parse_error "Directory '$opt_workdir' is not writeable."
fi
else
opt_workdir_cleanup=:
opt_workdir=$(mktemp -d /var/lib/copr-builder/build-XXXXX) \
|| opt_parse_error "can't create workdir"
fi
case $opt_host_resolv in
1|yes|True|true) opt_host_resolv=True ;;
0|no|False|false) opt_host_resolv=False ;;
'') ;; # default: unchanged
*) opt_parse_error "Use --host-resolv=1 (or 0)" ;;
esac
$opt_parse_success
eval "$(crudini --format=sh --get "$opt_config" copr_builder)"
success=:
for opt in branching
do
eval "test -z \"\$$opt\"" \
&& error "missing config option $opt" \
&& success=false
done
$success
# Construct 'rpkg' options, use them as '$@' later.
set -- --verbose --debug --config "$opt_config"
# TODO: drop this hack after https://pagure.io/rpkg/pull-request/212
set -- "$@" --release rhel-7.2
cd "$opt_workdir"
info "Copying system mock configuration"
configs_dir=$opt_resultdir/configs
mkdir -p "$configs_dir"
cp /etc/mock/site-defaults.cfg "$configs_dir"
cp "/etc/mock/$opt_chroot.cfg" "$configs_dir"
info "Downloading changed mock config from frontend"
if $opt_download_mock_config; then
copr --config "$opt_config" mock-config "$opt_copr" "$opt_chroot" \
> "$configs_dir"/changed.cfg
fi
mock+=(--configdir "$configs_dir" -r changed)
if test -n "$opt_host_resolv"; then
echo "config_opts['use_host_resolv'] = $opt_host_resolv" \
>> "$configs_dir"/changed.cfg
fi
info "Obtain sources from dist-git"
stdout_wrap rpkg "$@" clone -a "$opt_copr/$opt_package" pkg-git
(
cd pkg-git
git checkout "$opt_revision"
stdout_wrap rpkg "$@" --module-name "$opt_copr/$opt_package" sources
)
info "Generate SRPM in mock $opt_chroot"
stdout_wrap "${mock[@]}" \
--buildsrpm \
--spec pkg-git/"$opt_package".spec \
--sources pkg-git \
--resultdir intermediate-srpm \
--no-cleanup-after
info "Generate RPM in cached mock chroot"
stdout_wrap "${mock[@]}" \
--rebuild intermediate-srpm/"$opt_package"*.src.rpm \
--resultdir "$opt_resultdir" \
--no-clean
echo done > "$opt_resultdir"/success