root/build-tools/build-docker-images/build-stx-images.sh
Scott Little 7715e8bef2 Make builder nice to it's host
StarlingX build tools consume excessive cpu and io which can starve
non-build services, including kubernetes and calico. This results in
timeouts, restarts, and general instability of calico and kubernetes
which in turn can the build to fail. This affect is observed on the
main StarlingX build server.

The major build steps should employ 'nice' and 'ionice' to ensure
that critical services on the build host are not starved for cpu or io.

Depends-On: https://review.opendev.org/c/starlingx/tools/+/939799
Closes-bug: 2095512
Change-Id: Ib904fb28d403c958c10311d5940cc96c752728b4
Signed-off-by: Scott Little <scott.little@windriver.com>
2025-01-22 09:09:43 -05:00

1217 lines
38 KiB
Bash
Executable File

#!/bin/bash
#
# Copyright (c) 2018-2019 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# This utility builds the StarlingX container images
#
MY_SCRIPT_DIR=$(dirname $(readlink -fv $0))
# Required env vars
if [ -z "${MY_WORKSPACE}" -o -z "${MY_REPO}" ]; then
echo "Environment not setup for builds" >&2
exit 1
fi
source ${MY_REPO}/build-tools/git-utils.sh
# make this process nice
renice -n 10 -p $$
ionice -c 3 -p $$
SUPPORTED_OS_ARGS=('centos' 'debian' 'distroless')
OS=
OS_LABEL=
BUILD_STREAM=stable
IMAGE_VERSION=$(date --utc '+%Y.%m.%d.%H.%M') # Default version, using timestamp
PREFIX=dev
LATEST_PREFIX=""
PUSH=no
HTTP_PROXY=""
HTTPS_PROXY=""
NO_PROXY=""
DOCKER_USER=${USER}
DOCKER_REGISTRY=
PULL_BASE=yes
BASE=
WHEELS=
WHEELS_PY2=
CLEAN=no
export USE_DOCKER_CACHE=no
TAG_LATEST=no
TAG_LIST_FILE=
TAG_LIST_LATEST_FILE=
DEFAULT_SPICE_REPO="https://gitlab.freedesktop.org/spice/spice-html5"
declare -a ONLY
declare -a SKIP
declare -i MAX_ATTEMPTS=1
declare -i RETRY_DELAY=30
declare -a RESULTS_BUILT
declare -a RESULTS_PUSHED
declare -a RESULTS_FAILED
declare -a RESULTS_PUSH_FAILED
function usage {
cat >&2 <<EOF
Usage:
$(basename $0)
Options:
--os: Specify base OS (valid options: ${SUPPORTED_OS_ARGS[@]})
--os-label: Use this string as part of image tags, log file names and
image record file names, in place of OS, eg:
"--os distroless --os-label debian" would look for
"distroless" build recipes, but tag them as "debian"
--version: Specify version for output image
--stream: Build stream, stable or dev (default: stable)
--base: Specify base docker image (required option)
-N,--no-pull-base: Don't pull base image before building; this will use
your local base image if one exists, without overwriting
it by "docker pull"
--wheels: Specify path to wheels tarball or image, URL or docker tag
(required when building loci projects)
--wheels-py2: Use this wheels tarball for Python2 projects
(default: work out from --wheels)
--wheels-alternate: same as --wheels-py2
--push: Push to docker repo
--http_proxy: Set proxy <URL>:<PORT>, urls splitted with ","
--https_proxy: Set proxy <URL>:<PORT>, urls splitted with ","
--no_proxy: Set proxy <URL>, urls splitted with ","
--user: Docker repo userid
--registry: Docker registry
--prefix: Prefix on the image tag (default: dev)
--latest: Add a 'latest' tag when pushing
--latest-prefix: Alternative prefix on the latest image tag
--clean: Remove image(s) from local registry
--only <image> : Only build the specified image(s). Multiple images
can be specified with a comma-separated list, or with
multiple --only arguments.
--skip <image> : Skip building the specified image(s). Multiple images
can be specified with a comma-separated list, or with
multiple --skip arguments.
--attempts <count>
How many times to try a failed build command (default: 1)
--retry-delay <seconds>
Sleep this many seconds between retries (default: 30)
--cache: Allow docker to use filesystem cache when building
CAUTION: this option may ignore locally-generated
packages and is meant for debugging the build
scripts.
EOF
}
function is_in {
local search=$1
shift
for v in $*; do
if [ "${search}" = "${v}" ]; then
return 0
fi
done
return 1
}
function starts_with {
local str="$1"
local prefix="$2"
[[ "${str#$prefix}" != "$str" ]]
}
function is_empty {
test $# -eq 0
}
function url_basename {
# http://foo/bar.tar?xxx#yyy => bar.tar
echo "$1" | sed -r -e 's/[?#].*//' -e 's#.*/##'
}
function local_path_to_url {
local path="$1"
local abs_path
abs_path="$(readlink -fv "$path")" || exit 1
local repo_root
repo_root="$(readlink -ev "$MY_REPO_ROOT_DIR")" || exit 1
local workspace_root
workspace_root="$(readlink -ev "$MY_WORKSPACE")" || exit 1
local dflt_port
if starts_with "$abs_path" "$repo_root" ; then
dflt_port="8089"
elif starts_with "$abs_path" "$workspace_root" ; then
dflt_port="8088"
else
echo "ERROR: $path: path must start with \$MY_REPO_ROOT_DIR or \$MY_WORKSPACE" >&2
exit 1
fi
if [[ -n "$BUILDER_FILES_URL" ]] ; then
echo "${BUILDER_FILES_URL}${path}"
else
echo "http://${HOSTNAME}:${dflt_port}${path}"
fi
}
#
# get_git: Clones a git into a subdirectory of ${WORKDIR}, and
# leaves you in that directory. On error the directory
# is undefined.
#
function get_git {
local git_repo=${1}
local git_ref=${2}
local git_patches=${@:3} # Take remaining args as patch list
local git_name
git_name=$(basename ${git_repo} | sed 's/[.]git$//')
if [ -z ${git_name} ] || \
[ "${git_name}" == "." ] || \
[ "${git_name}" == ".." ] || \
[ "${git_name}" == "*" ]; then
echo "git repo appears to be invalid: ${git_repo}. Aborting..." >&2
return 1
fi
if [ ! -d ${WORKDIR}/${git_name} ]; then
cd ${WORKDIR}
with_retries -d ${RETRY_DELAY} ${MAX_ATTEMPTS} $SHELL -c "rm -rf ${git_name}.clone_tmp && git clone --recursive ${git_repo} ${git_name}.clone_tmp"
if [ $? -ne 0 ]; then
rm -rf ${git_name}.clone_tmp
echo "Failed to clone ${git_repo}. Aborting..." >&2
return 1
fi
mv ${git_name}.clone_tmp ${git_name}
cd $git_name
git checkout ${git_ref}
if [ $? -ne 0 ]; then
echo "Failed to checkout '${git_name}' base ref: ${git_ref}" >&2
echo "Aborting..." >&2
return 1
fi
# Apply any patches
for p in ${git_patches}; do
git am ${p}
if [ $? -ne 0 ]; then
echo "Failed to apply ${p} in ${git_name}" >&2
echo "Aborting..." >&2
return 1
fi
done
else
cd ${WORKDIR}/${git_name}
with_retries -d ${RETRY_DELAY} ${MAX_ATTEMPTS} git fetch
if [ $? -ne 0 ]; then
echo "Failed to fetch '${git_name}'. Aborting..." >&2
return 1
fi
git checkout ${git_ref}
if [ $? -ne 0 ]; then
echo "Failed to checkout '${git_name}' base ref: ${git_ref}" >&2
echo "Aborting..." >&2
return 1
fi
# Apply any patches
for p in ${git_patches}; do
git am ${p}
if [ $? -ne 0 ]; then
echo "Failed to apply ${p} in ${git_name}" >&2
echo "Aborting..." >&2
return 1
fi
done
fi
return 0
}
function get_loci {
# Use a specific HEAD of loci, to provide a stable builder
local LOCI_REF="efccd0a853879ac6af6066eda09792d0d3afe9c0"
local LOCI_REPO="https://github.com/openstack/loci.git"
local ORIGWD=${PWD}
get_git ${LOCI_REPO} ${LOCI_REF}
if [ $? -ne 0 ]; then
echo "Failed to clone or update loci. Aborting..." >&2
cd ${ORIGWD}
return 1
fi
cd ${ORIGWD}
return 0
}
function patch_loci {
echo "Patching ${WORKDIR}/loci/Dockerfile" >&2
( cd "${WORKDIR}/loci" && git am $( \ls -1 $MY_SCRIPT_DIR/loci/patches/*.patch | sort ) ; ) || exit 1
# clear wheels dir
\rm -rf "${WORKDIR}/loci/stx-wheels/"* || exit 1
}
function update_image_record {
# Update the image record file with a new/updated entry
local LABEL=$1
local TAG=$2
local FILE=$3
grep -q "/${LABEL}:" ${FILE}
if [ $? -eq 0 ]; then
# Update the existing record
sed -i "s#.*/${LABEL}:.*#${TAG}#" ${FILE}
else
# Add a new record
echo "${TAG}" >> ${FILE}
fi
}
function post_build {
#
# Common utility function called from image build functions to run post-build steps.
#
local image_build_file=$1
local LABEL=$2
local build_image_name=$3
# Get additional supported args
#
# To avoid polluting the environment and impacting
# other builds, we're going to explicitly grab specific
# variables from the directives file. While this does
# mean the file is sourced repeatedly, it ensures we
# don't get junk.
local CUSTOMIZATION
CUSTOMIZATION=$(source ${image_build_file} && echo ${CUSTOMIZATION})
# Default IMAGE_UPDATE_VER to 0, if not set
local -i IMAGE_UPDATE_VER
IMAGE_UPDATE_VER=$(source ${image_build_file} && echo ${IMAGE_UPDATE_VER:-0})
local IMAGE_TAG_VERSIONED="${IMAGE_TAG}.${IMAGE_UPDATE_VER}"
if [ -n "${CUSTOMIZATION}" ]; then
local -a PROXY_ARGS=
if [ ! -z "$HTTP_PROXY" ]; then
PROXY_ARGS+=(--env http_proxy=$HTTP_PROXY)
fi
if [ ! -z "$HTTPS_PROXY" ]; then
PROXY_ARGS+=(--env https_proxy=$HTTPS_PROXY)
fi
if [ ! -z "$NO_PROXY" ]; then
PROXY_ARGS+=(--env no_proxy=$NO_PROXY)
fi
docker run ${PROXY_ARGS[@]} --entrypoint /bin/bash --name ${USER}_update_img ${build_image_name} -c "${CUSTOMIZATION}"
if [ $? -ne 0 ]; then
echo "Failed to add customization for ${LABEL}... Aborting"
RESULTS_FAILED+=(${LABEL})
docker rm ${USER}_update_img
return 1
fi
docker commit --change='CMD ["bash"]' ${USER}_update_img ${build_image_name}
if [ $? -ne 0 ]; then
echo "Failed to commit customization for ${LABEL}... Aborting"
RESULTS_FAILED+=(${LABEL})
docker rm ${USER}_update_img
return 1
fi
docker rm ${USER}_update_img
fi
if [ "${OS}" = "centos" ]; then
# Record python modules and packages
docker run --entrypoint /bin/bash --rm ${build_image_name} -c 'rpm -qa | sort' \
> ${WORKDIR}/${LABEL}-${OS_LABEL}-${BUILD_STREAM}.rpmlst
docker run --entrypoint /bin/bash --rm ${build_image_name} -c 'pip freeze 2>/dev/null | sort' \
> ${WORKDIR}/${LABEL}-${OS_LABEL}-${BUILD_STREAM}.piplst
fi
RESULTS_BUILT+=(${build_image_name})
if [ "${PUSH}" = "yes" ]; then
local push_tag="${DOCKER_REGISTRY}${DOCKER_USER}/${LABEL}:${IMAGE_TAG_VERSIONED}"
docker tag ${build_image_name} ${push_tag}
with_retries -d ${RETRY_DELAY} ${MAX_ATTEMPTS} docker push ${push_tag}
if [[ $? -ne 0 ]] ; then
echo "Failed to push ${push_tag} ... Aborting"
RESULTS_PUSH_FAILED+=(${LABEL})
return 1
fi
RESULTS_PUSHED+=(${push_tag})
update_image_record ${LABEL} ${push_tag} ${TAG_LIST_FILE}
if [ "$TAG_LATEST" = "yes" ]; then
local latest_tag="${DOCKER_REGISTRY}${DOCKER_USER}/${LABEL}:${IMAGE_TAG_LATEST}"
docker tag ${push_tag} ${latest_tag}
with_retries -d ${RETRY_DELAY} ${MAX_ATTEMPTS} docker push ${latest_tag}
if [[ $? -ne 0 ]] ; then
echo "Failed to push ${latest_tag} ... Aborting"
RESULTS_PUSH_FAILED+=(${LABEL})
return 1
fi
RESULTS_PUSHED+=(${latest_tag})
update_image_record ${LABEL} ${latest_tag} ${TAG_LIST_LATEST_FILE}
fi
fi
}
function cleanup_loci_failure {
# When loci fails, it leaves behind a stopped container and a none:none image.
# This function looks for those stopped containers to clean up after a failure.
local container
local image
local extra_fields
docker ps --no-trunc -f status=exited | grep /opt/loci/scripts/install.sh \
| while read container image extra_fields; do
echo "Cleaning loci build container and image: ${container} ${image}"
docker rm ${container}
docker image rm ${image}
done
}
function build_image_loci {
local image_build_file=$1
# Get the supported args
#
# To avoid polluting the environment and impacting
# other builds, we're going to explicitly grab specific
# variables from the directives file. While this does
# mean the file is sourced repeatedly, it ensures we
# don't get junk.
local LABEL
LABEL=$(source ${image_build_file} && echo ${LABEL})
local PROJECT
PROJECT=$(source ${image_build_file} && echo ${PROJECT})
local PROJECT_REPO
PROJECT_REPO=$(source ${image_build_file} && echo ${PROJECT_REPO})
local PROJECT_REF
PROJECT_REF=$(source ${image_build_file} && echo ${PROJECT_REF})
local PROJECT_UID
PROJECT_UID=$(source ${image_build_file} && echo ${PROJECT_UID})
local PROJECT_GID
PROJECT_GID=$(source ${image_build_file} && echo ${PROJECT_GID})
local PIP_PACKAGES
PIP_PACKAGES=$(source ${image_build_file} && echo ${PIP_PACKAGES})
local UPGRADE_PIP_PACKAGES
UPGRADE_PIP_PACKAGES=$(source ${image_build_file} && echo ${UPGRADE_PIP_PACKAGES})
local DIST_PACKAGES
DIST_PACKAGES=$(source ${image_build_file} && echo ${DIST_PACKAGES})
local PROFILES
PROFILES=$(source ${image_build_file} && echo ${PROFILES})
local PYTHON3
PYTHON3=$(source ${image_build_file} && echo ${PYTHON3})
local MIRROR_LOCAL
MIRROR_LOCAL=$(source ${image_build_file} && echo ${MIRROR_LOCAL})
local SPICE_REPO
SPICE_REPO=$(source ${image_build_file} && echo ${SPICE_REPO})
local SPICE_REF
SPICE_REF=$(source ${image_build_file} && echo ${SPICE_REF})
local DIST_REPOS
DIST_REPOS=$(source ${image_build_file} && echo ${DIST_REPOS})
local NON_UNIQUE_SYSTEM_ACCOUNT
NON_UNIQUE_SYSTEM_ACCOUNT=$(source ${image_build_file} && echo ${NON_UNIQUE_SYSTEM_ACCOUNT})
local UPDATE_SYSTEM_ACCOUNT
UPDATE_SYSTEM_ACCOUNT=$(source ${image_build_file} && echo ${UPDATE_SYSTEM_ACCOUNT})
echo "Building ${LABEL}"
local ORIGWD=${PWD}
if [ "${MIRROR_LOCAL}" = "yes" ]; then
# Setup a local mirror of PROJECT_REPO
local BARE_CLONES=${WORKDIR}/bare_clones
mkdir -p ${BARE_CLONES}
if [ $? -ne 0 ]; then
echo "Failed to create ${BARE_CLONES}" >&2
RESULTS_FAILED+=(${LABEL})
return 1
fi
local CLONE_DIR=${BARE_CLONES}/${PROJECT}.git
# Remove prior clone dir, if it exists
\rm -rf ${CLONE_DIR}
echo "Creating bare clone of ${PROJECT_REPO} for ${LABEL} build..."
if [ -n "${PROJECT_REF}" ]; then
echo "PROJECT_REF specified is ${PROJECT_REF}..."
with_retries -d ${RETRY_DELAY} ${MAX_ATTEMPTS} $SHELL -c "rm -rf ${CLONE_DIR}.clone_tmp && git clone --no-local --bare ${PROJECT_REPO} ${CLONE_DIR}.clone_tmp" \
&& mv ${CLONE_DIR}.clone_tmp ${CLONE_DIR} \
&& cd ${PROJECT_REPO} \
&& git push --force ${CLONE_DIR} HEAD:refs/heads/${PROJECT_REF} \
&& mv ${CLONE_DIR}/hooks/post-update.sample ${CLONE_DIR}/hooks/post-update \
&& chmod a+x ${CLONE_DIR}/hooks/post-update \
&& cd ${CLONE_DIR} \
&& git update-server-info \
&& cd ${ORIGWD}
else
with_retries -d ${RETRY_DELAY} ${MAX_ATTEMPTS} $SHELL -c "rm -rf ${CLONE_DIR}.clone_tmp && git clone --no-local --bare ${PROJECT_REPO} ${CLONE_DIR}.clone_tmp" \
&& mv ${CLONE_DIR}.clone_tmp ${CLONE_DIR} \
&& cd ${PROJECT_REPO} \
&& mv ${CLONE_DIR}/hooks/post-update.sample ${CLONE_DIR}/hooks/post-update \
&& chmod a+x ${CLONE_DIR}/hooks/post-update \
&& cd ${CLONE_DIR} \
&& git update-server-info \
&& cd ${ORIGWD}
fi
if [ $? -ne 0 ]; then
rm -rf ${CLONE_DIR}.clone_tmp
echo "Failed to clone ${PROJECT_REPO}... Aborting ${LABEL} build"
RESULTS_FAILED+=(${LABEL})
cd ${ORIGWD}
return 1
fi
PROJECT_REPO="$(local_path_to_url "${CLONE_DIR}")" || exit 1
fi
local -a BUILD_ARGS=
BUILD_ARGS=(--build-arg PROJECT=${PROJECT})
BUILD_ARGS+=(--build-arg PROJECT_REPO=${PROJECT_REPO})
BUILD_ARGS+=(--build-arg FROM=${BASE})
if [ "${PYTHON3}" == "no" ] ; then
echo "Python2 service ${LABEL}"
BUILD_ARGS+=(--build-arg WHEELS=${WHEELS_PY2})
else
echo "Python3 service ${LABEL}"
BUILD_ARGS+=(--build-arg WHEELS=${WHEELS})
fi
if [ ! -z "$HTTP_PROXY" ]; then
BUILD_ARGS+=(--build-arg http_proxy=$HTTP_PROXY)
fi
if [ ! -z "$HTTPS_PROXY" ]; then
BUILD_ARGS+=(--build-arg https_proxy=$HTTPS_PROXY)
fi
if [ ! -z "$NO_PROXY" ]; then
BUILD_ARGS+=(--build-arg no_proxy=$NO_PROXY)
fi
if [ -n "${PROJECT_REF}" ]; then
BUILD_ARGS+=(--build-arg PROJECT_REF=${PROJECT_REF})
fi
if [ -n "${PROJECT_UID}" ]; then
BUILD_ARGS+=(--build-arg UID="${PROJECT_UID}")
fi
if [ -n "${PROJECT_GID}" ]; then
BUILD_ARGS+=(--build-arg GID="${PROJECT_GID}")
fi
if [ -n "${PIP_PACKAGES}" ]; then
BUILD_ARGS+=(--build-arg PIP_PACKAGES="${PIP_PACKAGES}")
fi
if [ -n "${UPGRADE_PIP_PACKAGES}" ]; then
BUILD_ARGS+=(--build-arg UPGRADE_PIP_PACKAGES="${UPGRADE_PIP_PACKAGES}")
fi
if [ -n "${DIST_PACKAGES}" ]; then
BUILD_ARGS+=(--build-arg DIST_PACKAGES="${DIST_PACKAGES}")
fi
if [ -n "${PROFILES}" ]; then
BUILD_ARGS+=(--build-arg PROFILES="${PROFILES}")
fi
if [ -n "${PYTHON3}" ]; then
BUILD_ARGS+=(--build-arg PYTHON3="${PYTHON3}")
fi
if [ -n "${SPICE_REPO}" ]; then
BUILD_ARGS+=(--build-arg SPICE_REPO="${SPICE_REPO}")
else
BUILD_ARGS+=(--build-arg SPICE_REPO="${DEFAULT_SPICE_REPO}")
fi
if [ -n "${SPICE_REF}" ]; then
BUILD_ARGS+=(--build-arg SPICE_REF="${SPICE_REF}")
fi
if [ -n "${DIST_REPOS}" ]; then
BUILD_ARGS+=(--build-arg DIST_REPOS="${DIST_REPOS}")
fi
if [ -n "${NON_UNIQUE_SYSTEM_ACCOUNT}" ]; then
BUILD_ARGS+=(--build-arg NON_UNIQUE_SYSTEM_ACCOUNT="${NON_UNIQUE_SYSTEM_ACCOUNT}")
fi
if [ -n "${UPDATE_SYSTEM_ACCOUNT}" ]; then
BUILD_ARGS+=(--build-arg UPDATE_SYSTEM_ACCOUNT="${UPDATE_SYSTEM_ACCOUNT}")
fi
# Disable build cache
if [[ "$USE_DOCKER_CACHE" != "yes" ]] ; then
BUILD_ARGS+=("--no-cache")
fi
local build_image_name="${USER}/${LABEL}:${IMAGE_TAG_BUILD}"
with_retries -d ${RETRY_DELAY} ${MAX_ATTEMPTS} docker build ${WORKDIR}/loci \
"${BUILD_ARGS[@]}" \
--tag ${build_image_name} 2>&1 | tee ${WORKDIR}/docker-${LABEL}-${OS_LABEL}-${BUILD_STREAM}.log
if [ ${PIPESTATUS[0]} -ne 0 ]; then
echo "Failed to build ${LABEL}... Aborting"
RESULTS_FAILED+=(${LABEL})
cleanup_loci_failure
return 1
fi
if [ ${OS} = "centos" ]; then
# For images with apache, we need a workaround for paths
echo "${PROFILES}" | grep -q apache
if [ $? -eq 0 ]; then
docker run --entrypoint /bin/bash --name ${USER}_update_img ${build_image_name} -c '\
ln -s /var/log/httpd /var/log/apache2 && \
ln -s /var/run/httpd /var/run/apache2 && \
ln -s /etc/httpd /etc/apache2 && \
ln -s /etc/httpd/conf.d /etc/apache2/conf-enabled && \
ln -s /etc/httpd/conf.modules.d /etc/apache2/mods-available && \
ln -s /usr/sbin/httpd /usr/sbin/apache2 && \
ln -s /etc/httpd/conf.d /etc/apache2/sites-enabled \
'
if [ $? -ne 0 ]; then
echo "Failed to add apache workaround for ${LABEL}... Aborting"
RESULTS_FAILED+=(${LABEL})
docker rm ${USER}_update_img
return 1
fi
docker commit --change='CMD ["bash"]' ${USER}_update_img ${build_image_name}
if [ $? -ne 0 ]; then
echo "Failed to commit apache workaround for ${LABEL}... Aborting"
RESULTS_FAILED+=(${LABEL})
docker rm ${USER}_update_img
return 1
fi
docker rm ${USER}_update_img
fi
fi
post_build ${image_build_file} ${LABEL} ${build_image_name}
}
function build_image_docker {
local image_build_file=$1
# Get the supported args
#
local LABEL
LABEL=$(source ${image_build_file} && echo ${LABEL})
local DOCKER_CONTEXT
DOCKER_CONTEXT=$(source ${image_build_file} && echo ${DOCKER_CONTEXT})
local DOCKER_FILE
DOCKER_FILE=$(source ${image_build_file} && echo ${DOCKER_FILE})
local DOCKER_REPO
DOCKER_REPO=$(source ${image_build_file} && echo ${DOCKER_REPO})
local DOCKER_REF
DOCKER_REF=$(source ${image_build_file} && echo ${DOCKER_REF:-master})
# DOCKER_PATCHES is a list of patch files, relative to the local dir
local DOCKER_PATCHES
DOCKER_PATCHES=$(source ${image_build_file} && for p in ${DOCKER_PATCHES}; do echo $(dirname ${image_build_file})/${p}; done)
echo "Building ${LABEL}"
local real_docker_context
local real_docker_file
if [ -n "${DOCKER_REPO}" ]; then
local ORIGWD=${PWD}
echo "get_git '${DOCKER_REPO}' '${DOCKER_REF}' '${DOCKER_PATCHES}'"
get_git "${DOCKER_REPO}" "${DOCKER_REF}" "${DOCKER_PATCHES}"
if [ $? -ne 0 ]; then
echo "Failed to clone or update ${DOCKER_REPO}. Aborting..." >&2
RESULTS_FAILED+=(${LABEL})
cd ${ORIGWD}
return 1
fi
real_docker_file="${PWD}/Dockerfile"
if [ ! -f ${real_docker_file} ]; then
real_docker_file=$(find ${PWD} -type f -name Dockerfile | head -n 1)
fi
real_docker_context=$(dirname ${real_docker_file})
cd ${ORIGWD}
else
if [ -n "${DOCKER_CONTEXT}" ]; then
real_docker_context=$(dirname ${image_build_file})/${DOCKER_CONTEXT}
else
real_docker_context=$(dirname ${image_build_file})/docker
fi
if [ -n "${DOCKER_FILE}" ]; then
real_docker_file=$(dirname ${image_build_file})/${DOCKER_FILE}
else
real_docker_file=${real_docker_context}/Dockerfile
fi
fi
# Check for a Dockerfile
if [ ! -f ${real_docker_file} ]; then
echo "${real_docker_file} not found" >&2
RESULTS_FAILED+=(${LABEL})
return 1
fi
# Possible design option: Make a copy of the real_docker_context dir in BUILDDIR
local build_image_name="${USER}/${LABEL}:${IMAGE_TAG_BUILD}"
local -a BASE_BUILD_ARGS
BASE_BUILD_ARGS+=(${real_docker_context})
BASE_BUILD_ARGS+=(--file ${real_docker_file})
BASE_BUILD_ARGS+=(--build-arg "BASE=${BASE}")
if [ ! -z "$HTTP_PROXY" ]; then
BASE_BUILD_ARGS+=(--build-arg http_proxy=$HTTP_PROXY)
fi
if [ ! -z "$HTTPS_PROXY" ]; then
BASE_BUILD_ARGS+=(--build-arg https_proxy=$HTTPS_PROXY)
fi
if [ ! -z "$NO_PROXY" ]; then
BASE_BUILD_ARGS+=(--build-arg no_proxy=$NO_PROXY)
fi
if [[ "$USE_DOCKER_CACHE" != "yes" ]] ; then
BASE_BUILD_ARGS+=("--no-cache")
fi
BASE_BUILD_ARGS+=(--tag ${build_image_name})
with_retries -d ${RETRY_DELAY} ${MAX_ATTEMPTS} docker build ${BASE_BUILD_ARGS[@]} 2>&1 | tee ${WORKDIR}/docker-${LABEL}-${OS_LABEL}-${BUILD_STREAM}.log
if [ ${PIPESTATUS[0]} -ne 0 ]; then
echo "Failed to build ${LABEL}... Aborting"
RESULTS_FAILED+=(${LABEL})
return 1
fi
post_build ${image_build_file} ${LABEL} ${build_image_name}
}
function build_image_script {
local image_build_file=$1
# Get the supported args
#
local LABEL
LABEL=$(source ${image_build_file} && echo ${LABEL})
local SOURCE_REPO
SOURCE_REPO=$(source ${image_build_file} && echo ${SOURCE_REPO})
local SOURCE_REF
SOURCE_REF=$(source ${image_build_file} && echo ${SOURCE_REF:-master})
local COMMAND
COMMAND=$(source ${image_build_file} && echo ${COMMAND})
local SCRIPT
SCRIPT=$(source ${image_build_file} && echo ${SCRIPT})
local ARGS
ARGS=$(source ${image_build_file} && echo ${ARGS})
# SOURCE_PATCHES is a list of patch files, relative to the local dir
local SOURCE_PATCHES
SOURCE_PATCHES=$(source ${image_build_file} && for p in ${SOURCE_PATCHES}; do echo $(dirname ${image_build_file})/${p}; done)
# Validate the COMMAND option
SUPPORTED_COMMAND_ARGS=('bash')
local VALID_COMMAND=1
for supported_command in ${SUPPORTED_COMMAND_ARGS[@]}; do
if [ "$COMMAND" = "${supported_command}" ]; then
VALID_COMMAND=0
break
fi
done
if [ ${VALID_COMMAND} -ne 0 ]; then
echo "Unsupported build command specified: ${COMMAND}" >&2
echo "Supported command options: ${SUPPORTED_COMMAND_ARGS[@]}" >&2
RESULTS_FAILED+=(${LABEL})
return 1
fi
# Validate the SCRIPT file existed
if [ ! -f $(dirname ${image_build_file})/${SCRIPT} ]; then
echo "${SCRIPT} not found" >&2
RESULTS_FAILED+=(${LABEL})
return 1
fi
echo "Building ${LABEL}"
local ORIGWD=${PWD}
echo "get_git '${SOURCE_REPO}' '${SOURCE_REF}' '${SOURCE_PATCHES}'"
get_git "${SOURCE_REPO}" "${SOURCE_REF}" "${SOURCE_PATCHES}"
if [ $? -ne 0 ]; then
echo "Failed to clone or update ${SOURCE_REPO}. Aborting..." >&2
RESULTS_FAILED+=(${LABEL})
cd ${ORIGWD}
return 1
fi
cp $(dirname ${image_build_file})/${SCRIPT} ${SCRIPT}
local build_image_name="${USER}/${LABEL}:${IMAGE_TAG_BUILD}"
with_retries -d ${RETRY_DELAY} ${MAX_ATTEMPTS} ${COMMAND} ${SCRIPT} ${ARGS} ${build_image_name} $HTTP_PROXY $HTTPS_PROXY $NO_PROXY 2>&1 | tee ${WORKDIR}/docker-${LABEL}-${OS_LABEL}-${BUILD_STREAM}.log
if [ ${PIPESTATUS[0]} -ne 0 ]; then
echo "Failed to build ${LABEL}... Aborting"
RESULTS_FAILED+=(${LABEL})
return 1
fi
# check docker image
cd ${ORIGWD}
post_build ${image_build_file} ${LABEL} ${build_image_name}
}
function build_image {
local image_build_file=$1
# Get the builder
local BUILDER
BUILDER=$(source ${image_build_file} && echo ${BUILDER})
case ${BUILDER} in
loci)
build_image_loci ${image_build_file}
return $?
;;
docker)
build_image_docker ${image_build_file}
return $?
;;
script)
build_image_script ${image_build_file}
return $?
;;
*)
echo "Unsupported BUILDER in ${image_build_file}: ${BUILDER}" >&2
return 1
;;
esac
}
OPTS=$(getopt -o hN -l help,os:,os-label:,version:,release:,stream:,push,http_proxy:,https_proxy:,no_proxy:,user:,registry:,base:,wheels:,wheels-alternate:,wheels-py2:,only:,skip:,prefix:,latest,latest-prefix:,clean,cache,attempts:,retry-delay:,no-pull-base -- "$@")
if [ $? -ne 0 ]; then
usage
exit 1
fi
eval set -- "${OPTS}"
while true; do
case $1 in
--)
# End of getopt arguments
shift
break
;;
--base)
BASE=$2
shift 2
;;
--os)
OS=$2
shift 2
;;
--os-label)
OS_LABEL=$2
shift 2
;;
--wheels)
WHEELS=$2
shift 2
;;
--wheels-alternate|--wheels-py2)
WHEELS_PY2=$2
shift 2
;;
--version)
IMAGE_VERSION=$2
shift 2
;;
--stream)
BUILD_STREAM=$2
shift 2
;;
--release) # Temporarily keep --release support as an alias for --stream
BUILD_STREAM=$2
shift 2
;;
--prefix)
PREFIX=$2
shift 2
;;
--latest-prefix)
LATEST_PREFIX=$2
shift 2
;;
--push)
PUSH=yes
shift
;;
--http_proxy)
HTTP_PROXY=$2
shift 2
;;
--https_proxy)
HTTPS_PROXY=$2
shift 2
;;
--no_proxy)
NO_PROXY=$2
shift 2
;;
--user)
DOCKER_USER=$2
shift 2
;;
--registry)
# Add a trailing / if needed
DOCKER_REGISTRY="${2%/}/"
shift 2
;;
--clean)
CLEAN=yes
shift
;;
--cache)
USE_DOCKER_CACHE=yes
shift
;;
--only)
# Read comma-separated values into array
ONLY+=(${2//,/ })
shift 2
;;
--skip)
# Read comma-separated values into array
SKIP+=(${2//,/ })
shift 2
;;
--latest)
TAG_LATEST=yes
shift
;;
--attempts)
MAX_ATTEMPTS=$2
shift 2
;;
--retry-delay)
RETRY_DELAY=$2
shift 2
;;
-N|--no-pull-base)
PULL_BASE=no
shift
;;
-h | --help )
usage
exit 1
;;
*)
usage
exit 1
;;
esac
done
# Validate the OS option
if [ -z "$OS" ] ; then
OS="$(ID= && source /etc/os-release 2>/dev/null && echo $ID || true)"
if [[ -z "$OS" ]] ; then
echo "Unable to determine OS, please re-run with \`--os' option" >&2
exit 1
fi
fi
VALID_OS=1
for supported_os in ${SUPPORTED_OS_ARGS[@]}; do
if [ "$OS" = "${supported_os}" ]; then
VALID_OS=0
break
fi
done
if [ ${VALID_OS} -ne 0 ]; then
echo "Unsupported OS specified: ${OS}" >&2
echo "Supported OS options: ${SUPPORTED_OS_ARGS[@]}" >&2
exit 1
fi
if [[ -z "$OS_LABEL" ]] ; then
OS_LABEL="$OS"
fi
if [ -z "${BASE}" ]; then
echo "Base image must be specified with --base option." >&2
exit 1
fi
# Guess WHEELS_PY2 if missing
if [[ -z "$WHEELS_PY2" && -n "$WHEELS" ]]; then
# http://foo/bar.tar?xxx#yyy => http://foo/bar-py2.tar?xxx#yyy
WHEELS_PY2="$(echo "$WHEELS" | sed -r 's,^([^#?]*)(\.tar)(\.gz|\.bz2|\.xz)?([#?].*)?$,\1-py2\2\3\4,i')"
if [[ "$WHEELS" == "$WHEELS_PY2" ]]; then
echo "Unable to guess --wheels-py2, please specify it explicitly" >&2
exit 1
fi
fi
# Resolve local wheel file names to absolute paths
for var in WHEELS WHEELS_PY2 ; do
# skip empty vars
[[ -n "${!var}" ]] || continue
# http(s) urls are supported by Loci directly -- skip
# See https://github.com/openstack/loci/blob/efccd0a853879ac6af6066eda09792d0d3afe9c0/scripts/fetch_wheels.py#L170
echo "${!var}" | grep -E -q -e '^https?:' && continue
# remove file:/ prefix if any
declare "$var=$(echo "${!var}" | sed -r 's#^file:/+##')"
# resolve it to an absolute path
declare "$var=$(readlink -fv "${!var}")" || exit 1
# convert it to a local URL
url="$(local_path_to_url "${!var}")" || exit 1
declare "$var=$url"
done
# Find the directives files
IMAGE_BUILD_FILES=()
function find_image_build_files {
local image_build_inc_file image_build_dir image_build_file
local -A all_labels
for image_build_inc_file in $(find ${GIT_LIST} -maxdepth 1 -name "${OS}_${BUILD_STREAM}_docker_images.inc"); do
basedir=$(dirname ${image_build_inc_file})
for image_build_dir in $(sed -e 's/#.*//' ${image_build_inc_file} | sort -u); do
for image_build_file in ${basedir}/${image_build_dir}/${OS}/*.${BUILD_STREAM}_docker_image; do
# Make sure image exists
if [[ ! -f "$image_build_file" ]] ; then
echo "ERROR: $image_build_file: file not found" >&2
echo "ERROR: $image_build_inc_file: referenced here" >&2
exit 1
fi
# reset & read image build directive vars
local BUILDER=
local PROJECT=
local LABEL=
local PYTHON3=
PROJECT="$(source ${image_build_file} && echo ${PROJECT})"
BUILDER="$(source ${image_build_file} && echo ${BUILDER})"
LABEL="$(source ${image_build_file} && echo ${LABEL})"
PYTHON3="$(source ${image_build_file} && echo ${PYTHON3})"
# make sure labels are unique
if [[ -n "${all_labels["$LABEL"]}" ]] ; then
echo "The following files define the same LABEL $LABEL" >&2
echo " ${all_labels["$LABEL"]}" >&2
echo " ${image_build_file}" >&2
exit 1
fi
all_labels["$LABEL"]="$image_build_file"
# skip images we don't want to build
if is_in "${PROJECT}" ${SKIP[@]} || is_in "${LABEL}" ${SKIP[@]}; then
continue
fi
if ! is_empty ${ONLY[@]} && ! is_in "${PROJECT}" ${ONLY[@]} && ! is_in "${LABEL}" ${ONLY[@]}; then
continue
fi
# loci builders require a wheels tarball
if [[ "${BUILDER}" == "loci" ]] ; then
# python3 projects require $WHEELS
if [[ ( -z "${PYTHON3}" || "${PYTHON3}" != "no" ) && -z "${WHEELS}" ]] ; then
echo "You are building python3 services with loci, but you didn't specify --wheels!" >&2
exit 1
# python2 projects require WHEELS_PY2
elif [[ "${PYTHON3}" == "no" && -z "${WHEELS_PY2}" ]] ; then
echo "You are building python2 services with loci, but you didn't specify --wheels-py2!" >&2
exit 1
fi
fi
# Save image build file in the global list
IMAGE_BUILD_FILES+=("$image_build_file")
done
done
done
}
find_image_build_files
IMAGE_TAG="${OS_LABEL}-${BUILD_STREAM}"
IMAGE_TAG_LATEST="${IMAGE_TAG}-latest"
if [ -n "${LATEST_PREFIX}" ]; then
IMAGE_TAG_LATEST="${LATEST_PREFIX}-${IMAGE_TAG_LATEST}"
elif [ -n "${PREFIX}" ]; then
IMAGE_TAG_LATEST="${PREFIX}-${IMAGE_TAG_LATEST}"
fi
if [ -n "${PREFIX}" ]; then
IMAGE_TAG="${PREFIX}-${IMAGE_TAG}"
fi
IMAGE_TAG_BUILD="${IMAGE_TAG}-build"
if [ -n "${IMAGE_VERSION}" ]; then
IMAGE_TAG="${IMAGE_TAG}-${IMAGE_VERSION}"
fi
WORKDIR=${MY_WORKSPACE}/std/build-images
mkdir -p ${WORKDIR}
if [ $? -ne 0 ]; then
echo "Failed to create ${WORKDIR}" >&2
exit 1
fi
TAG_LIST_FILE=${WORKDIR}/images-${OS_LABEL}-${BUILD_STREAM}-versioned.lst
TAG_LIST_LATEST_FILE=${WORKDIR}/images-${OS_LABEL}-${BUILD_STREAM}-latest.lst
if [ "${PUSH}" = "yes" ]; then
if is_empty ${ONLY[@]} && is_empty ${SKIP[@]}; then
# Reset image record files, since we're building everything
echo -n > ${TAG_LIST_FILE}
if [ "$TAG_LATEST" = "yes" ]; then
echo -n > ${TAG_LIST_LATEST_FILE}
fi
fi
fi
# Check to see if the BASE image is already pulled
docker images --format '{{.Repository}}:{{.Tag}}' ${BASE} | grep -q "^${BASE}$"
BASE_IMAGE_PRESENT=$?
# Pull the image anyway, to ensure it's up to date
if [[ "$PULL_BASE" == "yes" ]] ; then
with_retries -d ${RETRY_DELAY} ${MAX_ATTEMPTS} docker pull ${BASE} || exit 1
fi
# Download loci, if needed.
get_loci
if [ $? -ne 0 ]; then
# Error is reported by the function already
exit 1
fi
patch_loci
# Replace mod_wsgi dependency and add rh_python36_mod_wsgi in loci/bindep.txt for python3 package
# refer to patch https://review.opendev.org/#/c/718603/
sed -i 's/mod_wsgi \[platform\:rpm apache\]/mod_wsgi \[platform\:rpm apache \!python3\]/g' ${WORKDIR}/loci/bindep.txt
if ! (grep -q rh-python36-mod_wsgi ${WORKDIR}/loci/bindep.txt); then
echo 'rh-python36-mod_wsgi [platform:rpm !platform:suse (apache python3)]' >> ${WORKDIR}/loci/bindep.txt
fi
# Replace outdated mysql-client dependency for placement project with default-mysql-client.
# For context, refer to: https://review.opendev.org/c/starlingx/root/+/871705/
sed -i 's'/\
'mysql-client \[platform:dpkg placement\]'/\
'default-mysql-client \[platform:dpkg placement\]'/ ${WORKDIR}/loci/bindep.txt
# Build everything
for image_build_file in "${IMAGE_BUILD_FILES[@]}" ; do
# Failures are reported by the build functions
build_image ${image_build_file}
done
if [ "${CLEAN}" = "yes" -a ${#RESULTS_BUILT[@]} -gt 0 ]; then
# Delete the images
echo "Deleting images"
docker image rm ${RESULTS_BUILT[@]} ${RESULTS_PUSHED[@]}
if [ $? -ne 0 ]; then
# We don't want to fail the overall build for this, so just log it
echo "Failed to clean up images" >&2
fi
if [ ${BASE_IMAGE_PRESENT} -ne 0 ]; then
# The base image was not already present, so delete it
echo "Removing docker image ${BASE}"
docker image rm ${BASE}
if [ $? -ne 0 ]; then
echo "Failed to delete base image from docker" >&2
fi
fi
fi
RC=0
if [ ${#RESULTS_BUILT[@]} -gt 0 ]; then
echo "#######################################"
echo
echo "The following images were built:"
for i in ${RESULTS_BUILT[@]}; do
echo $i
done | sort
if [ ${#RESULTS_PUSHED[@]} -gt 0 ]; then
echo
echo "The following tags were pushed:"
for i in ${RESULTS_PUSHED[@]}; do
echo $i
done | sort
fi
fi
if [ ${#RESULTS_FAILED[@]} -gt 0 ]; then
echo
echo "#######################################"
echo
echo "There were ${#RESULTS_FAILED[@]} build failures:"
for i in ${RESULTS_FAILED[@]}; do
echo $i
done | sort
RC=1
fi
if [ ${#RESULTS_PUSH_FAILED[@]} -gt 0 ]; then
echo
echo "#######################################"
echo
echo "There were ${#RESULTS_PUSH_FAILED[@]} push failures:"
for i in ${RESULTS_PUSH_FAILED[@]}; do
echo $i
done | sort
RC=1
fi
exit ${RC}