#!/bin/bash set -e . $(dirname $0)/jenkins-helpers.sh # Start a local docker instance with the requested arch and distro # This script is meant to be executed from Jenkins jobs inside TCWG # lab. It prints shell commands meant to be executed in the parent # shell, consisting in: # - definition of ${CONTAINER}, used to prefix commands that you want # to run inside the container. # - definition of ${CONTAINER_CLEANUP}, a cleanup statement remove the # container on exit for instance # - definition of ${session_host} and ${session_port}, can be used for # a remote connexion to the container usage() { echo "Usage: $0 [--arch container-arch] --distro flavour [--docker_opts opts] [--dryrun true/false] [--label label] [--newuser username:[uid]] [--node node] [--prefix prefix] [--session-host host] [--session-name name] [--task {build|test}] [--user user] [--weight weight] [--verbose true/false]" echo echo " container-arch: architecture (eg: amd64, i386, arm64, armhf)" echo " distro: distribution (eg: trusty)" echo " dryrun: boolean, just print commands if true" echo " label: jenkins label; container is started on least-busy node; also sets container architecture" echo " newuser: new user to create inside container, [:] specification." echo " node: jenkins node; container is started on host mapped to the node" echo " prefix: prefix to prepend to output variables and functions" echo " session-host: hostname where the container will run, defaults to localhost" echo " useful if the name resolution does not work correctly" echo " session-name: session, in case the default '$BUILD_NUMBER-$JOB_NAME' is not suitable" echo " task: type of container (build or test, default=build)" echo " user: remote user to use in the container." echo " weight: container weight, reserves resources. Default=1" echo " verbose: whether enable verbose output. Default=false" exit 1 } # Save stdout/stderr file descriptors exec 3>&1 4>&2 # Make sure all output goes to stderr exec 1>&2 container_arch="default" distro="default" docker_opts= dryrun=false label= node= newuser= prefix= session_host= session_name= task="build" weight=1 user= verbose="false" while [ $# -ge 1 ] do case $1 in --arch) container_arch=$2 [ x${container_arch} = x ] && usage shift 2 ;; --distro) distro=$2 [ x${distro} = x ] && usage shift 2 ;; --docker_opts) docker_opts="$2" [ x"${docker_opts}" = x ] && usage shift 2 ;; --dryrun) dryrun=$2 [ x${dryrun} = x ] && usage [ $dryrun != false ] && [ $dryrun != true ] && usage shift 2 ;; --label) label=$2 [ x${label} = x ] && usage shift 2 ;; --node) node=$2 [ x${node} = x ] && usage shift 2 ;; --newuser) newuser="$2" [ x${newuser} = x ] && usage shift 2 ;; --prefix) prefix=$2 [ x${prefix} = x ] && usage shift 2 ;; --session-host) session_host=$2 [ x${session_host} = x ] && usage shift 2 ;; --session-name) session_name=$2 [ x${session_name} = x ] && usage shift 2 ;; --task) task=$2 [ x${task} = x ] && usage shift 2 ;; --user) user="$2"@ [ x${user} = x@ ] && usage shift 2 ;; --weight) weight=$2 [ x${weight} = x ] && usage shift 2 ;; --verbose) verbose=$2 [ x${verbose} = x ] && usage shift 2 ;; *) echo "Unsupported option: $1" usage ;; esac done if $verbose; then set -x; fi dryruncmd="" if $dryrun; then dryruncmd="echo" fi if [ x"$node" = x"" -a x"$label" != x"" ]; then node=$(print_node_with_least_containers "$label") if [ x"$node" = x"" ]; then echo "ERROR: Cannot find node for $label" exit 1 fi fi if [ x"$node" != x"" ]; then if [ x"$session_host" != x"" ]; then echo "--session_host conflicts with --node" usage fi session_host=$(print_host_for_node $node) fi if [ x"$session_host" = x"" ]; then # Get first FQDN. This name needs to have .tcwglab suffix for VPN'ed # machines and entries in .ssh/config for external machines. session_host=$(hostname -A | cut -d" " -f 1) arch_host="localhost" else arch_host="$session_host" fi if [ x"${container_arch}" = x"default" ]; then if [ x"$label" != x"" ]; then container_arch=$(print_arch_for_label "$label") else container_arch=$(print_arch_for_host "$arch_host") fi elif [ x"$label" != x"" ]; then echo "--arch conflicts with --label" usage fi if [ x"$session_name" = x ]; then # Set the default session_name, using BUILD_NUMBER and JOB_NAME, # as set by Jenkins. if [ "x$BUILD_NUMBER" != "x" -a "x$JOB_NAME" != "x" ]; then session_name="$BUILD_NUMBER-$JOB_NAME" else session_name="$USER-$(date +%Y%m%d-%H_%M_%S)" fi session_name=$(print_docker_name "$session_name") fi if [ x"$distro" = x"default" ]; then distro="trusty-tcwg-tested" fi image=linaro/ci-${container_arch}-tcwg-${task}-ubuntu:${distro} case "$session_host" in "localhost"*) docker_host="" ;; *) docker_host="$session_host" ;; esac DOCKER="ssh $session_host docker" $DOCKER pull $image SECURITY="--cap-add=SYS_PTRACE" # We need this because of a bug in libgo's configure script: # it would crash when testing "whether setcontext clobbers TLS # variables", and report neither "no" nor "yes", later making # configure fail. # Also, because the sanitizers need to disable ASLR during the tests # and docker needs to explicitly enable the process to do that on all # architectures. SECURITY="${SECURITY} --security-opt seccomp:unconfined" # Reserve resources according to weight and task nproc=$(ssh $session_host nproc --all) memory=$(print_memory_limit "$task" "$weight" "$nproc") pids=$(print_pids_limit "$task" "$weight") cpus=$(print_cpu_shares "$task" "$weight") if [ x"${JOB_NAME:+set}" = x"set" ]; then job_name="$JOB_NAME" fi bind_mounts=($(print_bind_mounts "$task")) bind_mounts_opt=() for bind_mount in "${bind_mounts[@]}"; do # Make sure all bind-mount directories exist. # If a host bind-mount dir doesn't exist, then docker creates it on # the host with root:root owner, which can't be removed by cleanup job. dir="${bind_mount%%:*}" ssh $session_host mkdir -p "$dir" docker_dir=$(print_docker_path "$dir") bind_mounts_opt=("${bind_mounts_opt[@]}" "-v" "$docker_dir:$bind_mount") done volume_mounts=($(print_volume_mounts "$job_name" "-$container_arch-$distro")) for mount in "${volume_mounts[@]}"; do bind_mounts_opt=("${bind_mounts_opt[@]}" "-v" "$mount") done docker_run=($DOCKER run --name $session_name -dtP \ "${bind_mounts_opt[@]}" \ ${SECURITY} \ --memory=${memory}M \ --pids-limit=${pids} \ --cpu-shares=${cpus} \ ${docker_opts} \ $image) echo "${docker_run[@]}" session_id=$("${docker_run[@]}") if [ x"$session_id" = x ]; then exit 1 fi # Remove the docker instance we have just created in case something # goes wrong. CONTAINER_CLEANUP="$DOCKER rm -fv ${session_id}" trap "exec 1>&3 2>&4 ; ${CONTAINER_CLEANUP}" EXIT if [ x"$newuser" != x"" ]; then $DOCKER exec "$session_id" new-user.sh --user $newuser fi session_port=$($DOCKER port $session_id 22 | cut -d: -f 2) # Wait until the ssh server is ready to serve connexions # Make sure connexion messages go to stderr, so that in case of # success stdout contains only the connexion info expected by the # caller. ret=0 wait_for_ssh_server ${user}$session_host $session_port || ret=$? if [ $ret -eq 1 ]; then echo SSH server did not respond, exiting exit 1 fi # Do not remove the container upon exit: it is now ready trap EXIT # Restore stdout/stderr exec 1>&3 2>&4 cat <