mirror of
				https://git.jami.net/savoirfairelinux/jami-client-android.git
				synced 2025-10-30 07:58:36 +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
 | 
