Modify smithy to facilitate multi-distribution support

* support for OEL 6.3+ added to smithy
* smithy no longer bootstraps as a side-effect of being run: it asks the user to
  set '--bootstrap' if necessary.
* bootstrap steps and (rpm, pip) package dependencies moved to configuration files
  under tools/bootstrap/$distribution
* common bootstrap code refactored
* rpm installs are now idempotent.
* smithy checksum now based on bootstrap config files
* --force also supported as a command-line flag

TODO:
* pypi installs should probably also be made idempotent
* CentOS support
This commit is contained in:
Daniel Mercer
2012-12-18 12:51:40 -08:00
parent 508d8610f1
commit 9f61f4adfb
3 changed files with 195 additions and 116 deletions

263
smithy
View File

@@ -2,8 +2,6 @@
shopt -s nocasematch shopt -s nocasematch
RHEL_VERSION=$(lsb_release -r | awk '{ print $2 }' | cut -d"." -f1)
EPEL_RPM_LIST="http://mirrors.kernel.org/fedora-epel/$RHEL_VERSION/i386"
YUM_OPTS="--assumeyes --nogpgcheck" YUM_OPTS="--assumeyes --nogpgcheck"
PIP_CMD="pip-python" PIP_CMD="pip-python"
@@ -19,112 +17,101 @@ if [ -n "$SUDO_USER" ]; then
fi fi
fi fi
ARGS="$@"
PWD=`pwd` PWD=`pwd`
if [ -z "$BOOT_FILES" ]; then if [ -z "$BOOT_FILES" ]; then
BOOT_FN=".anvil_bootstrapped" BOOT_FN=".anvil_bootstrapped"
BOOT_FILES="${PWD}/$BOOT_FN" BOOT_FILES="${PWD}/$BOOT_FN"
fi fi
cache_and_install_rpm_url()
{
url=${1:?"Error: rpm uri is undefined!"}
cachedir=${RPM_CACHEDIR:-'/tmp'}
rpm=$(basename $url)
if [ ! -f "$cachedir/$rpm" ]; then
echo "Downloading $rpm to $cachedir..."
curl -s $url -o "$cachedir/$rpm" || return 1
fi
install_rpm "$cachedir/$rpm"
return $?
}
install_rpm()
{
rpmstr=${1:?"Error: rpm to install is undefined!"}
rpm=$rpmstr
[ $(dirname $rpm) = '.' ] || rpm=$(rpm -qp $rpmstr 2> /dev/null )
rpm -q $rpm > /dev/null 2>&1 && return 0
echo "+ Installing rpm requirement '$rpm'"
yum install $YUM_OPTS "$rpmstr" 2>&1
return $?
}
install_pypi()
{
pypi=${1:?"Error: pypi to install is undefined!"}
# TODO: Figure out a way to make pypi installation idempotent --
# in the simple case we can simply return true if the package
# appears in the output of 'pip freeze' but this doesn't handle
# the 'pkg>=1.0' syntax. -I explicitly reinstalls.
$PIP_CMD install -U -I $pypi
return $?
}
bootstrap_epel() bootstrap_epel()
{ {
if [ -z "$EPEL_RPM_LIST" ]; then [ -z "$EPEL_RPM_URL" ] && return 0
return 0 cache_and_install_rpm_url $EPEL_RPM_URL
fi return $?
echo "+ Locating the EPEL rpm..."
if [ -z "$EPEL_RPM" ]; then
EPEL_RPM=$(curl -s "$EPEL_RPM_LIST/" | grep -io ">\s*epel.*.rpm\s*<" | grep -io "epel.*.rpm")
if [ $? -ne 0 ]; then
return 1
fi
fi
echo "+ Downloading $EPEL_RPM_LIST/$EPEL_RPM to /tmp/$EPEL_RPM"
wget -q -O "/tmp/$EPEL_RPM" "$EPEL_RPM_LIST/$EPEL_RPM"
if [ $? -ne 0 ]; then
return 1
fi
echo "+ Installing /tmp/$EPEL_RPM..."
output=$(yum install $YUM_OPTS -t "/tmp/$EPEL_RPM" 2>&1)
yum_code=$?
if [[ $output =~ "does not update installed package" ]]; then
# Check for this case directly since this seems to return
# a 1 status code even though nothing happened...
return 0
fi
return $yum_code
} }
clean_requires() bootstrap_packages()
{ {
# First remove comments and blank lines from said files [ -z "$PACKAGES" ] && return 0
if [ -f "tools/pkg-requires" ]; then for pkg in $PACKAGES; do
grep -Pv "(^\s*#.*$|^\s*$)" tools/pkg-requires > /tmp/anvil-pkg-requires format=$(echo $pkg | cut -d: -f1)
else name=$(echo $pkg | cut -d: -f2)
echo "" > /tmp/anvil-pkg-requires echo "+ Installing $format requirement '$name'"
fi install_$format $name
if [ -f "tools/pip-requires" ]; then if [ $? != 0 ]; then
grep -Pv "(^\s*#.*$|^\s*$)" tools/pip-requires > /tmp/anvil-pip-requires echo "Error: Installation of $format package '$name' failed!"
else return $?
echo "" > /tmp/anvil-pip-requires fi
fi done
} }
has_bootstrapped() require()
{ {
format=${1?"Error: Specify a format as the first arg to require!"}
name=${2?"Error: No name specified for required $format"}
case "$format" in
rpm|pypi)
PACKAGES="$PACKAGES $format:$name"
;;
*)
echo "Error: Smithy does not know how to handle $format requirements!"
exit 1
;;
esac
}
needs_bootstrap()
{
$BOOTSTRAP && return 0
checksums=$(get_checksums) checksums=$(get_checksums)
for i in $BOOT_FILES; do for i in $BOOT_FILES; do
if [ -f $i ]; then if [ -f $i ]; then
contents=`cat $i` contents=`cat $i`
if [ "$contents" == "$checksums" ]; then [ "$contents" = "$checksums" ] && return 1
return 0
fi
fi fi
done done
return 1 return 0
} }
get_checksums() get_checksums()
{ {
# Now checksum said files to be used in telling if said files have changed # used to tell if the file have changed
pkg_checksum=$(md5sum /tmp/anvil-pkg-requires) echo $(md5sum "$BSCONF_FILE")
pip_checksum=$(md5sum /tmp/anvil-pip-requires)
echo "$pkg_checksum"
echo "$pip_checksum"
}
bootstrap_rhel()
{
echo "Bootstrapping RHEL $1"
# EPEL provides most of the python dependencies for RHEL
bootstrap_epel
if [ $? -ne 0 ];
then
return 1
fi
# Install line by line since yum and pip
# work better when installed individually (error reporting
# and interdependency wise).
for line in `cat /tmp/anvil-pkg-requires`; do
echo "+ Installing package requirement '$line'"
yum install $YUM_OPTS $line 2>&1 > /dev/null
if [ $? -ne 0 ];
then
echo "Failed installing ${line}!!"
return 1
fi
done
for line in `cat /tmp/anvil-pip-requires`; do
echo "+ Installing pypi requirement '$line'"
$PIP_CMD install -U -I $line 2>&1 > /dev/null
if [ $? -ne 0 ];
then
echo "Failed installing ${line}!!"
return 1
fi
done
return 0
} }
run_smithy() run_smithy()
@@ -135,49 +122,93 @@ run_smithy()
puke() puke()
{ {
# TODO(harlowja) better way to do this?? cleaned_force=$(echo $FORCE | sed -e 's/\([A-Z]\)/\L\1/g;s/\s//g')
cleaned_force=$(python -c "f='$FORCE'; print(f.lower().strip())")
if [[ "$cleaned_force" == "yes" ]]; then if [[ "$cleaned_force" == "yes" ]]; then
run_smithy run_smithy
else else
echo "To run anyway set FORCE=yes and rerun." echo "To run anyway set FORCE=yes and rerun." >&2
exit 1 exit 1
fi fi
} }
clean_requires ## identify which bootstrap configuration file to use: either set
has_bootstrapped ## explicitly (BSCONF_FILE) or determined based on the os distribution:
if [ $? -eq 0 ]; then BSCONF_DIR=${BSCONF_DIR:-$(dirname $(readlink -f "$0"))/tools/bootstrap}
run_smithy TYPE=$(lsb_release -d | cut -f 2)
RELEASE=$(lsb_release -r | cut -f 2)
if [ -z "$BSCONF_FILE" ]; then
OSDIST=$(echo $TYPE | sed -e 's/release.*$//g;s/\s//g')
BSCONF_FILE="$BSCONF_DIR/$OSDIST"
fi fi
TYPE=$(lsb_release -d | cut -f 2) ARGS=
if [[ "$TYPE" =~ "Red Hat Enterprise Linux Server" ]]; then BOOTSTRAP=false
RH_VER=$(lsb_release -r | cut -f 2) # ad-hoc getopt to handle long opts. smithy opts are consumed while
BC_OK=$(echo "$RH_VER < 6" | bc) # those to anvil are copied through.
if [ "$BC_OK" == "1" ]; then while [ ! -z $1 ]; do
echo "This script must be ran on RHEL 6.0+ and not RHEL $RH_VER." case "$1" in
puke '--bootstrap')
fi BOOTSTRAP=true
bootstrap_rhel $RH_VER shift
if [ $? -eq 0 ]; then ;;
# Write the checksums of the requirement files '--force')
# which if new requirements are added will cause new checksums FORCE=yes
# and a new dependency install... shift
checksums=$(get_checksums) ;;
for i in $BOOT_FILES; do *)
echo -e "$checksums" > $i ARGS="$ARGS $1"
done shift
echo "Done bootstrapping; marked this as being completed in $BOOT_FILES" ;;
run_smithy esac
else done
echo "Bootstrapping RHEL $RH_VER failed!!!"
exit 1 if ! needs_bootstrap; then
fi run_smithy
else elif ! $BOOTSTRAP; then
echo "Anvil has not been tested on distribution '$TYPE'" echo "This system needs to be updatedin order to run anvil!" >&2
echo "Running 'sudo smithy --bootstrap' will attempt to do so." >&2
exit 1
fi
## Bootstrap smithy
if [ "$(id -u)" != "0" ]; then
echo "You must run 'smithy --bootstrap' with root privileges!" >&2
exit 1
fi
if [ ! -f $BSCONF_FILE ]; then
echo "Anvil has not been tested on distribution '$TYPE'" >&2
puke
fi
echo "Sourcing $BSCONF_FILE"
source $BSCONF_FILE
MIN_RELEASE=${MIN_RELEASE:?"Error: MIN_RELEASE is undefined!"}
SHORTNAME=${SHORTNAME:?"Error: SHORTNAME is undefined!"}
BC_OK=$(echo "$RELEASE < $MIN_RELEASE" | bc)
if [ "$BC_OK" == "1" ]; then
echo "This script must be run on $SHORTNAME $MIN_RELEASE+ and not $SHORTNAME $RELEASE." >&2
puke puke
fi fi
echo "Bootstrapping $SHORTNAME $RELEASE"
echo "Please wait..."
for step in ${STEPS:?"Error: STEPS is undefined!"}; do
bootstrap_${step}
if [ $? != 0 ]; then
echo "Bootstrapping $SHORTNAME $RELEASE failed." >&2
exit 1
fi
done
# Write the checksums of the bootstrap file
# which if new requirements are added will cause new checksums
# and a new dependency install...
checksum=$(get_checksums)
for i in $BOOT_FILES; do
echo -e $checksum > $i
done
echo "Success! Bootstrapped for $SHORTNAME $RELEASE"
exit 0

View File

@@ -0,0 +1,24 @@
## Bootstrap OEL 6.3+ for Openstack Anvil
SHORTNAME=OEL
MIN_RELEASE=6.3
STEPS="epel packages"
EPEL_RPM_URL="http://mirrors.kernel.org/fedora-epel/6/i386/epel-release-6-7.noarch.rpm"
## Package Requirements (Order matters!)
require rpm PyYAML
require rpm gcc
require rpm git
require rpm pylint
require rpm python
require rpm python-iso8601
require rpm python-netifaces
require rpm python-ordereddict
require rpm python-pip
require rpm python-progressbar
require rpm python-psutil
require pypi termcolor
require pypi iniparse
require pypi hgtools
require pypi 'keyring>=0.9.2'
# This matches the nova version and doesn't really
# need to be a strong dependency for anvil to work..
require pypi 'Cheetah==2.4.4'

View File

@@ -0,0 +1,24 @@
## Bootstrap for Redhat Enterprise Linux 6.x
SHORTNAME=RHEL
MIN_RELEASE=6.0
STEPS="epel packages"
EPEL_RPM_URL="http://mirrors.kernel.org/fedora-epel/6/i386/epel-release-6-7.noarch.rpm"
## Package Requirements (Order matters!)
require rpm PyYAML
require rpm gcc
require rpm git
require rpm pylint
require rpm python
require rpm python-iso8601
require rpm python-netifaces
require rpm python-ordereddict
require rpm python-pip
require rpm python-progressbar
require rpm python-psutil
require pypi termcolor
require pypi iniparse
require pypi hgtools
require pypi 'keyring>=0.9.2'
# This matches the nova version and doesn't really
# need to be a strong dependency for anvil to work..
require pypi 'Cheetah==2.4.4'