mirror of
https://git.jami.net/savoirfairelinux/jami-client-android.git
synced 2026-03-13 10:42:02 +08:00
470 lines
13 KiB
Bash
Executable File
470 lines
13 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# cqfd - a tool to wrap commands in controlled Docker containers
|
|
#
|
|
# Copyright (C) 2015-2018 Savoir-faire Linux, 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
|
|
set -e
|
|
|
|
PROGNAME=`basename $0`
|
|
VERSION=5.2.2-alpha
|
|
cqfddir=".cqfd"
|
|
cqfdrc=".cqfdrc"
|
|
cqfd_user='builder'
|
|
cqfd_user_home='/home/builder'
|
|
cqfd_user_cwd="$cqfd_user_home/src"
|
|
|
|
## usage() - print usage on stdout
|
|
usage() {
|
|
cat <<EOF
|
|
Usage: $PROGNAME [OPTION ARGUMENT] [COMMAND] [ARGUMENTS]
|
|
|
|
Options:
|
|
-f <file> Use file as config file (default .cqfdrc).
|
|
-d <directory> Use directory as cqfd directory (default .cqfd).
|
|
-C <directory> Use the specified working directory.
|
|
-b <flavor_name> Target a specific build flavor.
|
|
-q Turn on quiet mode
|
|
-v or --version Show version.
|
|
-h or --help Show this help text.
|
|
|
|
Commands:
|
|
init Initialize project build container
|
|
flavors List flavors from config file to stdout
|
|
run Run argument(s) inside build container
|
|
release Run argument(s) and release software
|
|
help Show this help text
|
|
|
|
By default, run is assumed, and the run command is the one
|
|
configured in .cqfdrc.
|
|
|
|
cqfd is Copyright (C) 2015-2018 Savoir-faire Linux, Inc.
|
|
|
|
This program comes with ABSOLUTELY NO WARRANTY. This is free
|
|
software, and you are welcome to redistribute it under the terms
|
|
of the GNU GPLv3 license; see the LICENSE for more informations.
|
|
EOF
|
|
}
|
|
|
|
# parse_ini_config_file()
|
|
# Ref: http://theoldschooldevops.com/2008/02/09/bash-ini-parser/
|
|
# arg$1: path to ini file
|
|
parse_ini_config_file() {
|
|
# bash 4.3 and later break compatibility
|
|
local is_compatibility_mode=false
|
|
if [ $BASH_VERSINFO -ge 4 -a ${BASH_VERSINFO[1]} -gt 2 ]; then
|
|
is_compatibility_mode=true
|
|
shopt -s compat42
|
|
fi
|
|
|
|
if ! ini="$(<$1)"; then # read the file
|
|
die "$1: No such file!"
|
|
fi
|
|
ini="${ini//[/\\[}" # escape [
|
|
ini="${ini//]/\\]}" # escape ]
|
|
IFS=$'\n' && ini=( ${ini} ) # convert to line-array
|
|
ini=( ${ini[*]//;*/} ) # remove comments with ;
|
|
ini=( ${ini[*]/\ =/=} ) # remove tabs before =
|
|
ini=( ${ini[*]/=\ /=} ) # remove tabs be =
|
|
ini=( ${ini[*]/\ =\ /=} ) # remove anything with a space around =
|
|
ini=( ${ini[*]/#\\[/\}$'\n'cfg.section.} ) # set section prefix
|
|
ini=( ${ini[*]/%\\]/ \(} ) # convert text2function (1)
|
|
ini=( ${ini[*]/%\(/ \( \)} ) # close array parenthesis
|
|
ini=( ${ini[*]/%\\ \)/ \\} ) # the multiline trick
|
|
ini=( ${ini[*]/%\( \)/\(\) \{} ) # convert text2function (2)
|
|
ini=( ${ini[*]/%\} \)/\}} ) # remove extra parenthesis
|
|
ini[0]="" # remove first element
|
|
ini[${#ini[*]} + 1]='}' # add the last brace
|
|
if ! eval "$(echo "${ini[*]}")" 2>/dev/null; then # eval the result
|
|
die "$1: Invalid ini-file!"
|
|
fi
|
|
|
|
# restore previous bash behaviour
|
|
if $is_compatibility_mode; then
|
|
shopt -u compat42
|
|
fi
|
|
}
|
|
|
|
## die() - exit when an error occured
|
|
# $@ messages and variables shown in the error message
|
|
die() {
|
|
echo "cqfd: fatal: $@" 1>&2
|
|
exit 1
|
|
}
|
|
|
|
# docker_build() - Initialize build container
|
|
docker_build() {
|
|
local dockerfile="${cqfddir}/${distro:-docker}/Dockerfile"
|
|
|
|
if [ ! -f $dockerfile ]; then
|
|
die "no Dockerfile found at location $dockerfile"
|
|
fi
|
|
if [ -z "$project_build_context" ]; then
|
|
docker build ${quiet:+-q} $CQFD_EXTRA_BUILD_ARGS -t "$docker_img_name" "$(dirname "$dockerfile")"
|
|
else
|
|
docker build ${quiet:+-q} $CQFD_EXTRA_BUILD_ARGS -t "$docker_img_name" "${project_build_context}" -f "$dockerfile"
|
|
fi
|
|
}
|
|
|
|
# docker_run() - run command in configured container
|
|
# A few implementation details:
|
|
#
|
|
# - The user executing the build commands inside the container is
|
|
# named after $cqfd_user, with the same uid/gid as your user to keep
|
|
# filesystem permissions in sync.
|
|
#
|
|
# - Your project's source directory is always mapped to $cqfd_user_cwd
|
|
#
|
|
# - Your ~/.ssh directory is mapped to ~${cqfd_user}/.ssh to provide
|
|
# access to the ssh keys (your build may pull authenticated git
|
|
# repos for example).
|
|
#
|
|
# arg$1: the command string to execute as $cqfd_user
|
|
#
|
|
docker_run() {
|
|
local interactive_options
|
|
|
|
if tty -s; then
|
|
interactive_options="-ti"
|
|
fi
|
|
|
|
# If possible, map cqfd_user from the calling user's
|
|
if [ -n "$USER" ]; then
|
|
cqfd_user="$USER"
|
|
fi
|
|
|
|
if [ -n "$HOME" ]; then
|
|
cqfd_user_home="$(cd $HOME; pwd)"
|
|
cqfd_user_cwd="$(pwd)"
|
|
fi
|
|
|
|
# Display a warning message if using no more supported options
|
|
if [ -n "$CQFD_EXTRA_VOLUMES" ]; then
|
|
die 'Warning: CQFD_EXTRA_VOLUMES is no more supported, use
|
|
CQFD_EXTRA_RUN_ARGS="-v <local_dir>:<container_dir>"'
|
|
fi
|
|
if [ -n "$CQFD_EXTRA_HOSTS" ]; then
|
|
die 'Warning: CQFD_EXTRA_HOSTS is no more supported, use
|
|
CQFD_EXTRA_RUN_ARGS="--add-host <hostname>:<IP_address>"'
|
|
fi
|
|
if [ -n "$CQFD_EXTRA_ENV" ]; then
|
|
die 'Warning: CQFD_EXTRA_ENV is no more supported, use
|
|
CQFD_EXTRA_RUN_ARGS="-e <var_name>=<value>"'
|
|
fi
|
|
if [ -n "$CQFD_EXTRA_PORTS" ]; then
|
|
die 'Warning: CQFD_EXTRA_PORTS is no more supported, use
|
|
CQFD_EXTRA_RUN_ARGS="-p <host_port>:<docker_port>"'
|
|
fi
|
|
|
|
# The user may set the user_extra_groups in the .cqfdrc
|
|
# file to add groups to the user in the container.
|
|
local group
|
|
if [ -n "$cqfd_extra_groups" ]; then
|
|
for group in $cqfd_extra_groups; do
|
|
# optional groupd id specified ("name:123")
|
|
if echo "$group" | grep -qE ":[0-9]+$"; then
|
|
CQFD_GROUPS+="${group} "
|
|
else
|
|
id=$(awk -F: "\$1 == \"$group\" { print \$3 }" /etc/group)
|
|
CQFD_GROUPS+="${group}:${id} "
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# The user may set the CQFD_EXTRA_RUN_ARGS environment variables
|
|
# to pass custom run arguments to his development container.
|
|
|
|
# Set HOME variable for the $cqfd_user, except if it was
|
|
# explicitely set via CQFD_EXTRA_RUN_ARGS
|
|
local home_env_var="HOME=$cqfd_user_home"
|
|
if echo "$CQFD_EXTRA_RUN_ARGS" | egrep -q "(-e[[:blank:]]*|--env[[:blank:]]+)HOME="; then
|
|
home_env_var=""
|
|
fi
|
|
|
|
tmp_launcher=$(make_launcher)
|
|
|
|
trap "rm -f $tmp_launcher" EXIT
|
|
|
|
docker run --privileged \
|
|
$CQFD_EXTRA_RUN_ARGS \
|
|
--rm \
|
|
--log-driver=none \
|
|
-v "$tmp_launcher":/bin/cqfd_launch \
|
|
-v ~/.ssh:"$cqfd_user_home"/.ssh \
|
|
-v "$PWD":"$cqfd_user_cwd" \
|
|
${home_env_var:+ -e "$home_env_var"} \
|
|
$interactive_options \
|
|
${SSH_AUTH_SOCK:+ -v $SSH_AUTH_SOCK:"$cqfd_user_home"/.sockets/ssh} \
|
|
${SSH_AUTH_SOCK:+ -e SSH_AUTH_SOCK="$cqfd_user_home"/.sockets/ssh} \
|
|
$docker_img_name cqfd_launch "$@" 2>&1
|
|
}
|
|
|
|
# make_archive(): Create a release package.
|
|
# Note: the --transform option passed to tar allows to move all the
|
|
# specified files at the root of the archive. Therefore, you shouldn't
|
|
# include two files with the same name in the list of files to
|
|
# archive.
|
|
make_archive() {
|
|
local tar_opts
|
|
|
|
if [ -z "$release_files" ]; then
|
|
die "No files to archive, check files in $cqfdrc"
|
|
fi
|
|
|
|
for file in $release_files; do
|
|
if [ ! -e $file ]; then
|
|
die "Cannot release: can't find $file"
|
|
fi
|
|
done
|
|
|
|
# template the generated archive's filename
|
|
local git_short=`git rev-parse --short HEAD 2>/dev/null`
|
|
local git_long=`git rev-parse HEAD 2>/dev/null`
|
|
local date_rfc3339=`date +"%Y-%m-%d"`
|
|
|
|
# default name for the archive if not set
|
|
if [ -z "$release_archive" ]; then
|
|
release_archive="%Po-%Pn.tar.xz"
|
|
fi
|
|
|
|
release_archive=`echo $release_archive |
|
|
sed -e 's!%%!%!g;
|
|
s!%Gh!'$git_short'!g;
|
|
s!%GH!'$git_long'!g;
|
|
s!%D3!'$date_rfc3339'!g;
|
|
s!%Po!'$project_org'!g;
|
|
s!%Pn!'$project_name'!g;
|
|
s!%Cf!'$flavor'!g;'`
|
|
|
|
# also replace variable names - beware with eval
|
|
eval release_archive=`echo $release_archive`
|
|
|
|
# setting tar_transform=yes will move files to the root of a tar archive
|
|
if [ "$release_transform" = "yes" ]; then
|
|
tar_opts='--transform s/.*\///g'
|
|
fi
|
|
|
|
# setting tar_options will add the following options to the tar
|
|
# command
|
|
if [ -n "$tar_options" ]; then
|
|
tar_opts="$tar_opts $tar_options"
|
|
fi
|
|
|
|
# support the following archive formats
|
|
case "$release_archive" in
|
|
*.tar.xz)
|
|
XZ_OPT=-9 tar $tar_opts -cJf \
|
|
"$release_archive" $release_files
|
|
;;
|
|
*.tar.gz)
|
|
tar $tar_opts -czf \
|
|
"$release_archive" $release_files
|
|
;;
|
|
*.zip)
|
|
zip -q -9 -r "$release_archive" $release_files
|
|
;;
|
|
*)
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# make_launcher - generate in-container launcher script
|
|
# return: the path to the launcher script on stdout
|
|
make_launcher()
|
|
{
|
|
local tmpfile=$(mktemp /tmp/tmp.XXXXXX)
|
|
|
|
chmod 0755 $tmpfile
|
|
cat >$tmpfile <<EOF
|
|
#!/bin/sh
|
|
# create container user to match expected environment
|
|
|
|
die () {
|
|
echo "error: \$*"
|
|
exit 1
|
|
}
|
|
|
|
test_cmd () {
|
|
command -v "\$1" > /dev/null 2>&1
|
|
}
|
|
|
|
debug () {
|
|
test -n "\$CQFD_DEBUG" && echo "debug: \$*"
|
|
}
|
|
|
|
# Check container requirements
|
|
test -x /bin/bash || { failed=1 && echo "error: /bin/bash does not exist or is not executable"; }
|
|
test_cmd groupadd || { failed=1 && echo "error: Missing command: groupadd"; }
|
|
test_cmd useradd || { failed=1 && echo "error: Missing command: useradd"; }
|
|
test_cmd usermod || { failed=1 && echo "error: Missing command: usermod"; }
|
|
test_cmd chown || { failed=1 && echo "error: Missing command: chown"; }
|
|
test_cmd sudo && has_sudo=1 || test_cmd su ||
|
|
{ failed=1 && echo "error: Missing command: su or sudo"; }
|
|
test -n "\$failed" &&
|
|
die "Some dependencies are missing from the container, see above messages."
|
|
|
|
# Add the host's user and group to the container, and adjust ownership.
|
|
groupadd -og $GROUPS -f builders || die "groupadd command failed."
|
|
useradd -s /bin/bash -ou $UID -g $GROUPS -d "$cqfd_user_home" $cqfd_user \
|
|
|| die "useradd command failed."
|
|
chown $UID:$GROUPS "$cqfd_user_home" || die "chown command failed."
|
|
|
|
# Add specified groups to cqfd_user
|
|
for g in ${CQFD_GROUPS}; do
|
|
group=\$(echo "\$g" | cut -d: -f1)
|
|
gid=\$(echo "\$g" | cut -d: -f2)
|
|
|
|
if [ -n "\$gid" ]; then
|
|
# create group with provided id ("name:123")
|
|
groupadd -og "\$gid" -f "\$group" || die "groupadd failed for \$group."
|
|
fi
|
|
|
|
usermod -a -G \$group $cqfd_user || die "usermod command failed while adding group \${group}."
|
|
done
|
|
|
|
# run the provided command in the working directory
|
|
cd "$cqfd_user_cwd" || die "Changing directory to \"$cqfd_user_cwd\" failed."
|
|
if [ -n "\$has_sudo" ]; then
|
|
# Use sudo to provide a controlling TTY for the executed command
|
|
debug "Using \"sudo\" to execute command \"\$@\" as user \"$cqfd_user\""
|
|
sudo -E -u $cqfd_user sh -c "\$@"
|
|
else
|
|
debug "Using \"su\" to execute command \"\$@\" as user \"$cqfd_user\""
|
|
su $cqfd_user -p -c "\$@"
|
|
fi
|
|
EOF
|
|
echo $tmpfile
|
|
}
|
|
|
|
# config_load() - load build settings from cqfdrc
|
|
# $1: optional "flavor" of the build, is a suffix of command.
|
|
config_load() {
|
|
IFS="$IFS" parse_ini_config_file "$cqfdrc"
|
|
|
|
cfg.section.project # load the [project] section
|
|
project_org="$org"
|
|
project_name="$name"
|
|
project_build_context="$build_context"
|
|
|
|
cfg.section.build # load the [build] section
|
|
|
|
# build parameters may be overriden by a flavor defined in the
|
|
# build section's 'flavors' parameter.
|
|
local flavor="$1"
|
|
if [ -n "$flavor" ]; then
|
|
if grep -qw "$flavor" <<< "$flavors"; then
|
|
cfg.section."$flavor" # load the [$flavor] section
|
|
else
|
|
die "flavor \"$flavor\" not found in flavors list"
|
|
fi
|
|
fi
|
|
|
|
build_cmd="$command"
|
|
cqfd_extra_groups="$user_extra_groups"
|
|
release_files="`eval echo $files`"
|
|
release_archive="$archive"
|
|
release_transform="$tar_transform"
|
|
tar_options="$tar_options"
|
|
|
|
# This will look like fooinc_reponame
|
|
if [ -n "$project_org" -a -n "$project_name" ]; then
|
|
docker_img_name="cqfd${USER:+_${USER}}_${project_org}_${project_name}"
|
|
else
|
|
die "project.org and project.name not configured"
|
|
fi
|
|
|
|
# Adapt things for a specific container
|
|
if [ -n "$distro" ]; then
|
|
docker_img_name+="_$distro"
|
|
fi
|
|
}
|
|
|
|
has_to_release=false
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
help|-h|"--help")
|
|
usage
|
|
exit 0
|
|
;;
|
|
version|-v|"--version")
|
|
echo $VERSION
|
|
exit 0
|
|
;;
|
|
init)
|
|
config_load $flavor
|
|
docker_build
|
|
exit $?
|
|
;;
|
|
flavors)
|
|
config_load
|
|
echo $flavors
|
|
exit 0
|
|
;;
|
|
-b)
|
|
shift
|
|
flavor="$1"
|
|
;;
|
|
-d)
|
|
shift
|
|
cqfddir="$1"
|
|
;;
|
|
-f)
|
|
shift
|
|
cqfdrc="$1"
|
|
;;
|
|
-C)
|
|
shift
|
|
cd "$1"
|
|
;;
|
|
-q)
|
|
quiet=true
|
|
;;
|
|
run|release)
|
|
if [ "$1" = "release" ]; then
|
|
has_to_release=true
|
|
fi
|
|
if [ $# -gt 1 ]; then
|
|
shift
|
|
build_cmd_alt="$@"
|
|
fi
|
|
break
|
|
;;
|
|
?*)
|
|
echo "Unknown command: $1"
|
|
usage
|
|
exit 1
|
|
;;
|
|
*)
|
|
# empty or no argument case
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
config_load $flavor
|
|
|
|
if [ -n "$build_cmd_alt" ]; then
|
|
build_cmd=$build_cmd_alt
|
|
elif [ -z "$build_cmd" ]; then
|
|
die "No build.command defined in $cqfdrc !"
|
|
fi
|
|
|
|
docker_run "$build_cmd"
|
|
|
|
if $has_to_release; then
|
|
make_archive
|
|
fi
|