root/build-tools/build-docker-images/update-stx-image.sh
Scott Little a4174a1e0a Remove CentOS/OpenSUSE build support
StarlingX stopped supporting CentOS builds in the after release 7.0.
This update will strip CentOS from our code base.  It will also
remove references to the failed OpenSUSE feature as well.

There is still one CentOS based docker image (n3000), so
this update will leave limited CentOS support in download
and docker image building tools.

Verified with a full Jenkins master branch build for Debian.
- download full and incremental
- package build full and incremental
- iso build
- build of base container image
- build all flock container images
- helm chart build.

Story: 2011110
Task: 49939
Change-Id: I57939d2026d7df76091e8658750b4bd0fa8e4f5f
Signed-off-by: Scott Little <scott.little@windriver.com>
2024-08-06 13:43:50 +00:00

533 lines
15 KiB
Bash
Executable File

#!/bin/bash
#
# Copyright (c) 2019 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# Utility for incremental updates to an image
#
MY_SCRIPT_DIR=$(dirname $(readlink -f $0))
source ${MY_SCRIPT_DIR}/../utils.sh
# Required env vars
if [ -z "${MY_WORKSPACE}" -o -z "${MY_REPO}" ]; then
echo "Environment not setup for builds" >&2
exit 1
fi
PUSH=no
HTTP_PROXY=""
HTTPS_PROXY=""
NO_PROXY=""
DOCKER_USER=
DOCKER_REGISTRY=
FILE_BASEDIR=${PWD}
FROM=
CLEAN=no
DIST_PACKAGES=
CUSTOMIZATION_SCRIPT=
UPDATE_ID="unnamed-update"
declare -i IMAGE_UPDATE_VER=
declare -a WHEELS
declare -a DIST_PACKAGES
declare -a MODULE_SRC
declare -a EXTRA_FILES
declare -i MAX_ATTEMPTS=1
declare -i RETRY_DELAY=30
function usage {
cat >&2 <<EOF
Usage:
$(basename $0)
This utility installs incremental updates to an existing image, allowing
the user to update or install python modules and software packages, or
to provide a customization script to make changes to the image.
Options to specify files or source can be used more than once, as needed,
or with wildcards if in quotes.
Options:
--version: Image update version
--file: Read update directives from a file
--from: Specify image to update
--wheel: Specify python wheel file
--module-src: Specify path to module source to install/update (dir or git repo)
Formats: dir[|version]
url[|branch][|version]
--pkg: Specify path to distro package to install/update (ie. deb)
--customize: Customization script
--extra: Extra file (to be accessible to customization script)
--push: Push to docker repo
--http_proxy: Set http proxy <URL>:<PORT>, urls splitted with ","
--https_proxy: Set https proxy <URL>:<PORT>, urls splitted with ","
--no_proxy: set bypass list for proxy <URL> urls splitted with ","
--user: Docker repo userid
--registry: Docker registry
--clean: Remove image(s) from local registry
--attempts <count>: Max attempts, in case of failure (default: 1)
--retry-delay <seconds>: Sleep between retries (default: 30)
--update-id: Update ID
EOF
}
function copy_files_to_workdir {
#
# Utility function to copy files to the workdir
#
local destdir=$1
shift
if [ ${#@} -le 0 ]; then
# No files in list, nothing to do
return 0
fi
mkdir -p ${destdir}
if [ $? -ne 0 ]; then
echo "Failed to create dir: ${destdir}" >&2
exit 1
fi
for f in $*; do
if [[ ${f} =~ ^(http|https|git): ]]; then
pushd ${destdir}
with_retries -d ${RETRY_DELAY} ${MAX_ATTEMPTS} wget ${f}
if [ $? -ne 0 ]; then
echo "Failed to download $f to ${destdir}" >&2
exit 1
fi
else
cp -v ${f} ${destdir}/
if [ $? -ne 0 ]; then
echo "Failed to copy files to ${destdir}" >&2
exit 1
fi
fi
done
}
function hardcode_python_module_version {
#
# Update a python module's setup.py to hardcode the version,
# allowing for pip to read the version without git installed
# inside the container.
#
local module_dir=$1
local module_ver=$2
if [ ! -f ${module_dir}/setup.py ]; then
# Nothing to do
return 0
fi
pushd ${module_dir}
grep -q 'pbr=True' ./setup.py
if [ $? -eq 0 ]; then
if [ -z "${module_ver}" ]; then
# Get the calculated version
module_ver=$(python ./setup.py --version)
fi
chmod u+w ./setup.py # just in case
sed -i "s/pbr=True/version='${module_ver}'/" ./setup.py
else
# This function can be extended in the future to support
# hardcoding/updating the version in modules that don't
# use PBR, if required.
echo "Module ($(basename ${module_dir})) does not have pbr=True." >&2
echo "Skipping updating version in code." >&2
fi
popd
}
function update_image_record {
# Update the image record file with a new/updated entry
local LABEL=$1
local TAG=$2
local FILE=$3
touch ${FILE}
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 read_params_from_file {
local FILE=$1
if [ ! -f "${FILE}" ]; then
echo "Specified file does not exist: ${FILE}" >&2
exit 1
fi
# Get parameters from file
#
# 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.
FROM=$(source ${FILE} && echo ${FROM})
IMAGE_UPDATE_VER=$(source ${FILE} && echo ${IMAGE_UPDATE_VER})
CUSTOMIZATION_SCRIPT=$(source ${FILE} && echo ${CUSTOMIZATION_SCRIPT})
WHEELS=($(source ${FILE} && echo ${WHEELS}))
DIST_PACKAGES=($(source ${FILE} && echo ${DIST_PACKAGES}))
MODULE_SRC=($(source ${FILE} && echo ${MODULE_SRC}))
EXTRA_FILES=($(source ${FILE} && echo ${EXTRA_FILES}))
FILE_BASEDIR=$(dirname ${FILE})
}
OPTS=$(getopt -o h -l help,file:,from:,wheel:,module-src:,pkg:,customize:,extra:,push,http_proxy:,https_proxy:,no_proxy:,user:,registry:,clean,attempts:,retry-delay:,update-id: -- "$@")
if [ $? -ne 0 ]; then
usage
exit 1
fi
eval set -- "${OPTS}"
while true; do
case $1 in
--)
# End of getopt arguments
shift
break
;;
--version)
IMAGE_UPDATE_VER=$2
shift 2
;;
--file)
read_params_from_file $2
shift 2
;;
--from)
FROM=$2
shift 2
;;
--wheel)
WHEELS+=($2)
shift 2
;;
--module-src)
MODULE_SRC+=($2)
shift 2
;;
--pkg)
DIST_PACKAGES+=($2)
shift 2
;;
--customize)
CUSTOMIZATION_SCRIPT=$2
shift 2
;;
--extra)
EXTRA_FILES+=($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
;;
--attempts)
MAX_ATTEMPTS=$2
shift 2
;;
--retry-delay)
RETRY_DELAY=$2
shift 2
;;
--update-id)
UPDATE_ID=$2
shift 2
;;
-h | --help )
usage
exit 1
;;
*)
usage
exit 1
;;
esac
done
UPDATE_DIR=${MY_WORKSPACE}/std/update-images/${UPDATE_ID}
if [ -z "${FROM}" ]; then
echo "Image must be specified with --from option." >&2
exit 1
fi
LABEL=$(basename ${FROM} | sed 's/:.*//')
# Update the image version
CUR_IMAGE_UPDATE_VER=$(echo "${FROM}" | sed -r 's/.*\.([0-9][0-9]*)$/\1/')
if [ -z "${IMAGE_UPDATE_VER}" -o ${IMAGE_UPDATE_VER} = 0 ]; then
# IMAGE_UPDATE_VER is not set, so increment the current version
IMAGE_UPDATE_VER=$((${CUR_IMAGE_UPDATE_VER}+1))
fi
# Determine new tag for updated image
if [ -z "${CUR_IMAGE_UPDATE_VER}" ]; then
# The original image doesn't have a .VER at the end of the tag,
# so append the original tag with .IMAGE_UPDATE_VER
UPDATED_IMAGE="${FROM}.${IMAGE_UPDATE_VER}"
else
# Replace the .VER in the original image tag with .IMAGE_UPDATE_VER
UPDATED_IMAGE=$(echo ${FROM} | sed "s/\.[0-9][0-9]*$/\.${IMAGE_UPDATE_VER}/")
fi
UPDATED_IMAGE_TAG=$(echo "${UPDATED_IMAGE}" | sed 's/.*://')
# If DOCKER_USER and DOCKER_REGISTRY are specified, modify the UPDATED_IMAGE accordingly
if [ -n "${DOCKER_REGISTRY}" -o -n "${DOCKER_USER}" ]; then
UPDATED_IMAGE="${DOCKER_REGISTRY}${DOCKER_USER:-${USER}}/${LABEL}:${UPDATED_IMAGE_TAG}"
fi
# Prepare the workspace for internal-update-stx-image.sh.
# The workspace will contain all files needed to install updates,
# structured in pip-packages, dist-packages, and extras directories
# as appropriate.
WORKDIR=${UPDATE_DIR}/$(basename ${UPDATED_IMAGE} | tr ':' '_')
if [ -e ${WORKDIR} ]; then
rm -rf ${WORKDIR}
fi
mkdir -p ${WORKDIR}
if [ $? -ne 0 ]; then
echo "Failed to create workdir: ${WORKDIR}" >&2
exit 1
fi
# Change dir in case relative file locations were used
pushd ${FILE_BASEDIR}
if [ -n "${CUSTOMIZATION_SCRIPT}" ]; then
if [ ! -f "${CUSTOMIZATION_SCRIPT}" ]; then
echo "Customization script not found: ${CUSTOMIZATION_SCRIPT}" >&2
exit 1
fi
# Copy the customization script
cp ${CUSTOMIZATION_SCRIPT} ${WORKDIR}/customize.sh
fi
copy_files_to_workdir ${WORKDIR}/extras ${EXTRA_FILES[@]}
copy_files_to_workdir ${WORKDIR}/pip-packages/wheels ${WHEELS[@]}
copy_files_to_workdir ${WORKDIR}/dist-packages ${DIST_PACKAGES[@]}
if [ ${#MODULE_SRC[@]} -gt 0 ]; then
MODULES_DIR=${WORKDIR}/pip-packages/modules
mkdir -p ${MODULES_DIR}
if [ $? -ne 0 ]; then
echo "Failed to create dir: ${MODULES_DIR}" >&2
exit 1
fi
for module_src in ${MODULE_SRC[@]}; do
src_location=$(echo "${module_src}" | awk -F'|' '{print $1}')
if [ -d "${src_location}" ]; then
# Module source is a directory, so copy it to the workspace
cp --recursive --dereference ${src_location} ${MODULES_DIR}
if [ $? -ne 0 ]; then
echo "Failed to copy dir: ${src_location}" >&2
exit 1
fi
module=$(basename ${src_location})
module_ver=$(echo "${module_src}" | awk -F'|' '{print $2}')
hardcode_python_module_version ${MODULES_DIR}/${module} ${module_ver}
elif [[ ${src_location} =~ ^(http|https|git): ]]; then
# Module source is a URL, so use git to clone it.
# For a git repo, the module_src is specified as:
# src_location|module_ref|module_ver
# where:
# src_location - the URL of the repo to be cloned
# module_ref - optional specification of branch or tag to be fetched
# module_ver - optional specification of version to hardcode
pushd ${MODULES_DIR}
git clone ${src_location}
if [ $? -ne 0 ]; then
echo "Failed to clone src: ${src_location}" >&2
exit 1
fi
popd
module=$(basename ${src_location} | sed 's/\.git$//')
if [ ! -d "${MODULES_DIR}/${module}" ]; then
echo "Module directory doesn't exist: ${MODULES_DIR}/${module}" >&2
exit 1
fi
module_ref=$(echo "${module_src}" | awk -F'|' '{print $2}')
if [ -n "${module_ref}" ]; then
pushd ${MODULES_DIR}/${module}
git fetch ${src_location} ${module_ref}
if [ $? -ne 0 ]; then
echo "Failed to fetch repo branch: ${module} ${module_ref}" >&2
exit 1
fi
git checkout FETCH_HEAD
if [ $? -ne 0 ]; then
echo "Failed to checkout FETCH_HEAD: ${module} ${module_ref}" >&2
exit 1
fi
popd
fi
module_ver=$(echo "${module_src}" | awk -F'|' '{print $3}')
hardcode_python_module_version ${MODULES_DIR}/${module} ${module_ver}
else
echo "Invalid module source reference: ${src_location}" >&2
exit 1
fi
done
fi
popd
# Finally, copy the internal-update-stx-image.sh script
cp ${MY_SCRIPT_DIR}/internal-update-stx-image.sh ${WORKDIR}/
# WORKDIR is setup, let's pull the image and update it
# Pull the image, even if already present, to ensure it's up to date
with_retries ${MAX_ATTEMPTS} docker image pull ${FROM}
if [ $? -ne 0 ]; then
echo "Failed to pull image: ${FROM}" >&2
exit 1
fi
# Get the existing CMD and ENTRYPOINT
ORIG_CMD=$(docker inspect --format='{{.Config.Cmd}}' ${FROM} | sed -e 's/^\[//' -e 's/\]$//')
ORIG_ENTRYPOINT=$(docker inspect --format='{{.Config.Entrypoint}}' ${FROM} | sed -e 's/^\[//' -e 's/\]$//')
# Format the CMD and ENTRYPOINT to be valid for docker commit change
FORMATTED_ORIG_CMD=""
FORMATTED_ORIG_ENTRYPOINT=""
for token in ${ORIG_CMD}; do
FORMATTED_ORIG_CMD="${FORMATTED_ORIG_CMD}, \"${token}\""
done
if [ -z "${FORMATTED_ORIG_CMD}" ]; then
FORMATTED_ORIG_CMD="[\"\"]"
else
FORMATTED_ORIG_CMD="[${FORMATTED_ORIG_CMD:2}]"
fi
for token in ${ORIG_ENTRYPOINT}; do
FORMATTED_ORIG_ENTRYPOINT="${FORMATTED_ORIG_ENTRYPOINT}, \"${token}\""
done
if [ -z "${FORMATTED_ORIG_ENTRYPOINT}" ]; then
FORMATTED_ORIG_ENTRYPOINT="[\"\"]"
else
FORMATTED_ORIG_ENTRYPOINT="[${FORMATTED_ORIG_ENTRYPOINT:2}]"
fi
# Get the OS NAME from /etc/os-release
OS_NAME=$(docker run --entrypoint /bin/bash --rm ${FROM} -c 'source /etc/os-release && echo ${NAME}')
# Run a container to install updates
UPDATE_CONTAINER=${USER}_${LABEL}_updater_$$
docker run --entrypoint /bin/bash --name ${UPDATE_CONTAINER} \
-v "${WORKDIR}":/image-update \
${FROM} \
-x -c ' bash -x /image-update/internal-update-stx-image.sh '
if [ $? -ne 0 ]; then
echo "Failed to update image: ${FROM}" >&2
exit 1
fi
# Commit the updated image
docker commit --change="CMD ${FORMATTED_ORIG_CMD}" --change="ENTRYPOINT ${FORMATTED_ORIG_ENTRYPOINT}" ${UPDATE_CONTAINER} ${UPDATED_IMAGE}
if [ $? -ne 0 ]; then
echo "Failed to commit updated image: ${UPDATE_CONTAINER}" >&2
docker rm ${UPDATE_CONTAINER} >/dev/null
exit 1
fi
# Remove the update container
docker rm ${UPDATE_CONTAINER} >/dev/null
if [ "${OS_NAME}" = "CentOS Linux" ]; then
# Record python modules and packages
docker run --entrypoint /bin/bash --rm ${UPDATED_IMAGE} -c 'rpm -qa' \
| sort > ${UPDATE_DIR}/${LABEL}-${UPDATED_IMAGE_TAG}.rpmlst
if [ ${PIPESTATUS[0]} -ne 0 ]; then
echo "Failed to query RPMs from: ${UPDATED_IMAGE}" >&2
exit 1
fi
docker run --entrypoint /bin/bash --rm ${UPDATED_IMAGE} -c 'pip freeze 2>/dev/null' \
| sort > ${UPDATE_DIR}/${LABEL}-${UPDATED_IMAGE_TAG}.piplst
if [ ${PIPESTATUS[0]} -ne 0 ]; then
echo "Failed to query python modules from: ${UPDATED_IMAGE}" >&2
exit 1
fi
fi
IMAGE_RECORD_FILE=${UPDATE_DIR}/image-updates.lst
update_image_record ${LABEL} ${UPDATED_IMAGE} ${IMAGE_RECORD_FILE}
if [ "${PUSH}" = "yes" ]; then
docker push ${UPDATED_IMAGE}
fi
if [ "${CLEAN}" = "yes" ]; then
docker image rm ${FROM} ${UPDATED_IMAGE}
if [ $? -ne 0 ]; then
echo "Failed to clean images from docker: ${FROM} ${UPDATED_IMAGE}" >&2
fi
fi
echo "Updated image: ${UPDATED_IMAGE}"
exit 0