diff options
author | Chad Smith <chad.smith@canonical.com> | 2018-06-21 14:32:27 -0600 |
---|---|---|
committer | Chad Smith <chad.smith@canonical.com> | 2018-06-21 14:32:27 -0600 |
commit | ba53ceb5a8a30c10951ec3ac49b8d6ebbe09a524 (patch) | |
tree | 5b68f0602daea648d48b3dc16809b0d66fb565d1 /tools | |
parent | 7d1e8976bba629f30da45e814a5a97e2f4b7de3d (diff) | |
parent | 2d6e4219db73e80c135efd83753f9302f778f08d (diff) | |
download | vyos-cloud-init-ba53ceb5a8a30c10951ec3ac49b8d6ebbe09a524.tar.gz vyos-cloud-init-ba53ceb5a8a30c10951ec3ac49b8d6ebbe09a524.zip |
merge from master at 18.3
Diffstat (limited to 'tools')
-rwxr-xr-x | tools/ds-identify | 111 | ||||
-rwxr-xr-x | tools/make-tarball | 15 | ||||
-rwxr-xr-x | tools/read-dependencies | 8 | ||||
-rwxr-xr-x | tools/run-centos | 340 | ||||
-rwxr-xr-x | tools/run-container | 590 |
5 files changed, 721 insertions, 343 deletions
diff --git a/tools/ds-identify b/tools/ds-identify index 9a2db5c4..ce0477a5 100755 --- a/tools/ds-identify +++ b/tools/ds-identify @@ -1,4 +1,5 @@ #!/bin/sh +# shellcheck disable=2015,2039,2162,2166 # # ds-identify is configured via /etc/cloud/ds-identify.cfg # or on the kernel command line. It takes primarily 2 inputs: @@ -186,6 +187,16 @@ block_dev_with_label() { return 0 } +ensure_sane_path() { + local t + for t in /sbin /usr/sbin /bin /usr/bin; do + case ":$PATH:" in + *:$t:*|*:$t/:*) continue;; + esac + PATH="${PATH:+${PATH}:}$t" + done +} + read_fs_info() { cached "${DI_BLKID_OUTPUT}" && return 0 # do not rely on links in /dev/disk which might not be present yet. @@ -210,7 +221,9 @@ read_fs_info() { # 'set --' will collapse multiple consecutive entries in IFS for # whitespace characters (\n, tab, " ") so we cannot rely on getting # empty lines in "$@" below. - IFS="$CR"; set -- $out; IFS="$oifs" + + # shellcheck disable=2086 + { IFS="$CR"; set -- $out; IFS="$oifs"; } for line in "$@"; do case "${line}" in @@ -258,7 +271,7 @@ read_virt() { is_container() { case "${DI_VIRT}" in - lxc|lxc-libvirt|systemd-nspawn|docker|rkt) return 0;; + container-other|lxc|lxc-libvirt|systemd-nspawn|docker|rkt) return 0;; *) return 1;; esac } @@ -310,6 +323,7 @@ read_dmi_product_serial() { DI_DMI_PRODUCT_SERIAL="$_RET" } +# shellcheck disable=2034 read_uname_info() { # run uname, and parse output. # uname is tricky to parse as it outputs always in a given order @@ -329,6 +343,7 @@ read_uname_info() { return $ret } fi + # shellcheck disable=2086 set -- $out DI_UNAME_KERNEL_NAME="$1" DI_UNAME_NODENAME="$2" @@ -356,7 +371,8 @@ parse_yaml_array() { # the fix was to quote the open bracket (val=${val#"["}) (LP: #1689648) val=${val#"["} val=${val%"]"} - IFS=","; set -- $val; IFS="$oifs" + # shellcheck disable=2086 + { IFS=","; set -- $val; IFS="$oifs"; } for tok in "$@"; do trim "$tok" unquote "$_RET" @@ -392,7 +408,7 @@ read_datasource_list() { fi if [ -z "$dslist" ]; then dslist=${DI_DSLIST_DEFAULT} - debug 1 "no datasource_list found, using default:" $dslist + debug 1 "no datasource_list found, using default: $dslist" fi DI_DSLIST=$dslist return 0 @@ -403,7 +419,8 @@ read_pid1_product_name() { cached "${DI_PID_1_PRODUCT_NAME}" && return [ -r "${PATH_PROC_1_ENVIRON}" ] || return out=$(tr '\0' '\n' <"${PATH_PROC_1_ENVIRON}") - IFS="$CR"; set -- $out; IFS="$oifs" + # shellcheck disable=2086 + { IFS="$CR"; set -- $out; IFS="$oifs"; } for tok in "$@"; do key=${tok%%=*} [ "$key" != "$tok" ] || continue @@ -470,6 +487,7 @@ nocase_equal() { [ "$1" = "$2" ] && return 0 local delim="-delim-" + # shellcheck disable=2018,2019 out=$(echo "$1${delim}$2" | tr A-Z a-z) [ "${out#*${delim}}" = "${out%${delim}*}" ] } @@ -546,11 +564,13 @@ check_config() { else files="$*" fi - set +f; set -- $files; set -f; + # shellcheck disable=2086 + { set +f; set -- $files; set -f; } if [ "$1" = "$files" -a ! -f "$1" ]; then return 1 fi local fname="" line="" ret="" found=0 found_fn="" + # shellcheck disable=2094 for fname in "$@"; do [ -f "$fname" ] || continue while read line; do @@ -600,7 +620,6 @@ dscheck_NoCloud() { *\ ds=nocloud*) return ${DS_FOUND};; esac - is_ibm_cloud && return ${DS_NOT_FOUND} for d in nocloud nocloud-net; do check_seed_dir "$d" meta-data user-data && return ${DS_FOUND} check_writable_seed_dir "$d" meta-data user-data && return ${DS_FOUND} @@ -611,11 +630,12 @@ dscheck_NoCloud() { return ${DS_NOT_FOUND} } +is_ds_enabled() { + local name="$1" pad=" ${DI_DSLIST} " + [ "${pad#* $name }" != "${pad}" ] +} + check_configdrive_v2() { - is_ibm_cloud && return ${DS_NOT_FOUND} - if has_fs_with_label CONFIG-2 config-2; then - return ${DS_FOUND} - fi # look in /config-drive <vlc>/seed/config_drive for a directory # openstack/YYYY-MM-DD format with a file meta_data.json local d="" @@ -630,6 +650,15 @@ check_configdrive_v2() { debug 1 "config drive seeded directory had only 'latest'" return ${DS_FOUND} fi + + local ibm_enabled=false + is_ds_enabled "IBMCloud" && ibm_enabled=true + debug 1 "is_ds_enabled(IBMCloud) = $ibm_enabled." + [ "$ibm_enabled" = "true" ] && is_ibm_cloud && return ${DS_NOT_FOUND} + + if has_fs_with_label CONFIG-2 config-2; then + return ${DS_FOUND} + fi return ${DS_NOT_FOUND} } @@ -786,7 +815,7 @@ ec2_read_strict_setting() { # 3. look for the key 'strict_id' (datasource/Ec2/strict_id) # only in cloud.cfg or cloud.cfg.d/EC2.cfg (case insensitive) local cfg="${PATH_ETC_CI_CFG}" cfg_d="${PATH_ETC_CI_CFG_D}" - if check_config strict_id $cfg "$cfg_d/*[Ee][Cc]2*.cfg"; then + if check_config strict_id "$cfg" "$cfg_d/*[Ee][Cc]2*.cfg"; then debug 2 "${_RET_fname} set strict_id to $_RET" return 0 fi @@ -971,12 +1000,14 @@ dscheck_SmartOS() { # joyent cloud has two virt types: kvm and container # on kvm, product name on joyent public cloud shows 'SmartDC HVM' # on the container platform, uname's version has: BrandZ virtual linux + # for container, we also verify that the socketfile exists to protect + # against embedded containers (lxd running on brandz) local smartdc_kver="BrandZ virtual linux" + local metadata_sockfile="${PATH_ROOT}/native/.zonecontrol/metadata.sock" dmi_product_name_matches "SmartDC*" && return $DS_FOUND - if [ "${DI_UNAME_KERNEL_VERSION}" = "${smartdc_kver}" ] && - [ "${DI_VIRT}" = "container-other" ]; then - return ${DS_FOUND} - fi + [ "${DI_UNAME_KERNEL_VERSION}" = "${smartdc_kver}" ] && + [ -e "${metadata_sockfile}" ] && + return ${DS_FOUND} return ${DS_NOT_FOUND} } @@ -993,7 +1024,7 @@ dscheck_Scaleway() { *\ scaleway\ *) return ${DS_FOUND};; esac - if [ -f ${PATH_ROOT}/var/run/scaleway ]; then + if [ -f "${PATH_ROOT}/var/run/scaleway" ]; then return ${DS_FOUND} fi @@ -1006,7 +1037,25 @@ dscheck_Hetzner() { } is_ibm_provisioning() { - [ -f "${PATH_ROOT}/root/provisioningConfiguration.cfg" ] + local pcfg="${PATH_ROOT}/root/provisioningConfiguration.cfg" + local logf="${PATH_ROOT}/root/swinstall.log" + local is_prov=false msg="config '$pcfg' did not exist." + if [ -f "$pcfg" ]; then + msg="config '$pcfg' exists." + is_prov=true + if [ -f "$logf" ]; then + if [ "$logf" -nt "$PATH_PROC_1_ENVIRON" ]; then + msg="$msg log '$logf' from current boot." + else + is_prov=false + msg="$msg log '$logf' from previous boot." + fi + else + msg="$msg log '$logf' did not exist." + fi + fi + debug 2 "ibm_provisioning=$is_prov: $msg" + [ "$is_prov" = "true" ] } is_ibm_cloud() { @@ -1130,6 +1179,7 @@ found() { } trim() { + # shellcheck disable=2048,2086 set -- $* _RET="$*" } @@ -1150,7 +1200,7 @@ _read_config() { # if no parameters are set, modifies _rc scoped environment vars. # if keyname is provided, then returns found value of that key. local keyname="${1:-_unset}" - local line="" hash="#" ckey="" key="" val="" + local line="" hash="#" key="" val="" while read line; do line=${line%%${hash}*} key="${line%%:*}" @@ -1228,7 +1278,8 @@ parse_policy() { local mode="" report="" found="" maybe="" notfound="" local oifs="$IFS" tok="" val="" - IFS=","; set -- $policy; IFS="$oifs" + # shellcheck disable=2086 + { IFS=","; set -- $policy; IFS="$oifs"; } for tok in "$@"; do val=${tok#*=} case "$tok" in @@ -1295,15 +1346,15 @@ manual_clean_and_existing() { } read_uptime() { - local up idle + local up _ _RET="${UNAVAILABLE}" - [ -f "$PATH_PROC_UPTIME" ] && - read up idle < "$PATH_PROC_UPTIME" && _RET="$up" + [ -f "$PATH_PROC_UPTIME" ] && read up _ < "$PATH_PROC_UPTIME" && + _RET="$up" return } _main() { - local dscheck="" ret_dis=1 ret_en=0 + local dscheck_fn="" ret_dis=1 ret_en=0 read_uptime debug 1 "[up ${_RET}s]" "ds-identify $*" @@ -1338,8 +1389,9 @@ _main() { return fi - # if there is only a single entry in $DI_DSLIST + # shellcheck disable=2086 set -- $DI_DSLIST + # if there is only a single entry in $DI_DSLIST if [ $# -eq 1 ] || [ $# -eq 2 -a "$2" = "None" ] ; then debug 1 "single entry in datasource_list ($DI_DSLIST) use that." found "$@" @@ -1372,6 +1424,7 @@ _main() { done debug 2 "found=${found# } maybe=${maybe# }" + # shellcheck disable=2086 set -- $found if [ $# -ne 0 ]; then if [ $# -eq 1 ]; then @@ -1387,6 +1440,7 @@ _main() { return fi + # shellcheck disable=2086 set -- $maybe if [ $# -ne 0 -a "${DI_ON_MAYBE}" != "none" ]; then debug 1 "$# datasources returned maybe: $*" @@ -1415,18 +1469,19 @@ _main() { *) error "Unexpected result";; esac debug 1 "$msg" - return $ret + return "$ret" } main() { local ret="" + ensure_sane_path [ -d "$PATH_RUN_CI" ] || mkdir -p "$PATH_RUN_CI" if [ "${1:+$1}" != "--force" ] && [ -f "$PATH_RUN_CI_CFG" ] && [ -f "$PATH_RUN_DI_RESULT" ]; then if read ret < "$PATH_RUN_DI_RESULT"; then if [ "$ret" = "0" ] || [ "$ret" = "1" ]; then debug 2 "used cached result $ret. pass --force to re-run." - return $ret; + return "$ret"; fi debug 1 "previous run returned unexpected '$ret'. Re-running." else @@ -1438,7 +1493,7 @@ main() { echo "$ret" > "$PATH_RUN_DI_RESULT" read_uptime debug 1 "[up ${_RET}s]" "returning $ret" - return $ret + return "$ret" } noop() { diff --git a/tools/make-tarball b/tools/make-tarball index 3197689f..8d540139 100755 --- a/tools/make-tarball +++ b/tools/make-tarball @@ -13,22 +13,28 @@ Usage: ${0##*/} [revision] create a tarball of revision (default HEAD) options: - -o | --output FILE write to file + -h | --help print usage + -o | --output FILE write to file + --orig-tarball Write file cloud-init_<version>.orig.tar.gz + --long Use git describe --long for versioning EOF } short_opts="ho:v" -long_opts="help,output:,long,verbose" +long_opts="help,output:,orig-tarball,long" getopt_out=$(getopt --name "${0##*/}" \ --options "${short_opts}" --long "${long_opts}" -- "$@") && eval set -- "${getopt_out}" || { Usage 1>&2; exit 1; } long_opt="" +orig_opt="" while [ $# -ne 0 ]; do cur=$1; next=$2 case "$cur" in + -h|--help) Usage; exit 0;; -o|--output) output=$next; shift;; --long) long_opt="--long";; + --orig-tarball) orig_opt=".orig";; --) shift; break;; esac shift; @@ -39,7 +45,10 @@ version=$(git describe --abbrev=8 "--match=[0-9]*" ${long_opt} $rev) archive_base="cloud-init-$version" if [ -z "$output" ]; then - output="$archive_base.tar.gz" + if [ ! -z "$orig_opt" ]; then + archive_base="cloud-init_$version" + fi + output="$archive_base$orig_opt.tar.gz" fi # when building an archiving from HEAD, ensure that there aren't any diff --git a/tools/read-dependencies b/tools/read-dependencies index 421f470a..b4656e69 100755 --- a/tools/read-dependencies +++ b/tools/read-dependencies @@ -51,6 +51,10 @@ MAYBE_RELIABLE_YUM_INSTALL = [ """, 'reliable-yum-install'] +ZYPPER_INSTALL = [ + 'zypper', '--non-interactive', '--gpg-auto-import-keys', 'install', + '--auto-agree-with-licenses'] + DRY_DISTRO_INSTALL_PKG_CMD = { 'centos': ['yum', 'install', '--assumeyes'], 'redhat': ['yum', 'install', '--assumeyes'], @@ -61,8 +65,8 @@ DISTRO_INSTALL_PKG_CMD = { 'redhat': MAYBE_RELIABLE_YUM_INSTALL, 'debian': ['apt', 'install', '-y'], 'ubuntu': ['apt', 'install', '-y'], - 'opensuse': ['zypper', 'install'], - 'suse': ['zypper', 'install'] + 'opensuse': ZYPPER_INSTALL, + 'suse': ZYPPER_INSTALL, } diff --git a/tools/run-centos b/tools/run-centos index cb241ee5..4506b20d 100755 --- a/tools/run-centos +++ b/tools/run-centos @@ -1,18 +1,17 @@ #!/bin/bash # This file is part of cloud-init. See LICENSE file for license information. -set -u - -VERBOSITY=0 -TEMP_D="" -KEEP=false -CONTAINER="" - -error() { echo "$@" 1>&2; } -fail() { [ $# -eq 0 ] || error "$@"; exit 1; } -errorrc() { local r=$?; error "$@" "ret=$r"; return $r; } +deprecated() { +cat <<EOF + ================ DEPRECATED ================ + | run-centos is deprecated. Please replace | + | your usage with tools/run-container . | + ================ DEPRECATED ================ +EOF +} Usage() { + deprecated cat <<EOF Usage: ${0##*/} [ options ] version @@ -34,319 +33,40 @@ Usage: ${0##*/} [ options ] version Example: * ${0##*/} --rpm --srpm --unittest 6 EOF + deprecated +EOF } bad_Usage() { Usage 1>&2; [ $# -eq 0 ] || error "$@"; return 1; } -cleanup() { - if [ -n "$CONTAINER" -a "$KEEP" = "false" ]; then - delete_container "$CONTAINER" - fi - [ -z "${TEMP_D}" -o ! -d "${TEMP_D}" ] || rm -Rf "${TEMP_D}" -} - -debug() { - local level=${1}; shift; - [ "${level}" -gt "${VERBOSITY}" ] && return - error "${@}" -} - - -inside_as() { - # inside_as(container_name, user, cmd[, args]) - # executes cmd with args inside container as user in users home dir. - local name="$1" user="$2" - shift 2 - if [ "$user" = "root" ]; then - inside "$name" "$@" - return - fi - local stuffed="" b64="" - stuffed=$(getopt --shell sh --options "" -- -- "$@") - stuffed=${stuffed# -- } - b64=$(printf "%s\n" "$stuffed" | base64 --wrap=0) - inside "$name" su "$user" -c \ - 'cd; eval set -- "$(echo '$b64' | base64 --decode)" && exec "$@"' -} - -inside_as_cd() { - local name="$1" user="$2" dir="$3" - shift 3 - inside_as "$name" "$user" sh -c 'cd "$0" && exec "$@"' "$dir" "$@" -} - -inside() { - local name="$1" - shift - lxc exec "$name" -- "$@" -} - -inject_cloud_init(){ - # take current cloud-init git dir and put it inside $name at - # ~$user/cloud-init. - local name="$1" user="$2" dirty="$3" - local changes="" top_d="" dname="cloud-init" pstat="" - local gitdir="" commitish="" - gitdir=$(git rev-parse --git-dir) || { - errorrc "Failed to get git dir in $PWD"; - return - } - local t=${gitdir%/*} - case "$t" in - */worktrees) - if [ -f "${t%worktrees}/config" ]; then - gitdir="${t%worktrees}" - fi - esac - - # attempt to get branch name. - commitish=$(git rev-parse --abbrev-ref HEAD) || { - errorrc "Failed git rev-parse --abbrev-ref HEAD" - return - } - if [ "$commitish" = "HEAD" ]; then - # detached head - commitish=$(git rev-parse HEAD) || { - errorrc "failed git rev-parse HEAD" - return - } - fi - - local local_changes=false - if ! git diff --quiet "$commitish"; then - # there are local changes not committed. - local_changes=true - if [ "$dirty" = "false" ]; then - error "WARNING: You had uncommitted changes. Those changes will " - error "be put into 'local-changes.diff' inside the container. " - error "To test these changes you must pass --dirty." - fi - fi - - debug 1 "collecting ${gitdir} ($dname) into user $user in $name." - tar -C "${gitdir}" -cpf - . | - inside_as "$name" "$user" sh -ec ' - dname=$1 - commitish=$2 - rm -Rf "$dname" - mkdir -p $dname/.git - cd $dname/.git - tar -xpf - - cd .. - git config core.bare false - out=$(git checkout $commitish 2>&1) || - { echo "failed git checkout $commitish: $out" 1>&2; exit 1; } - out=$(git checkout . 2>&1) || - { echo "failed git checkout .: $out" 1>&2; exit 1; } - ' extract "$dname" "$commitish" - [ "${PIPESTATUS[*]}" = "0 0" ] || { - error "Failed to push tarball of '$gitdir' into $name" \ - " for user $user (dname=$dname)" - return 1 - } - echo "local_changes=$local_changes dirty=$dirty" - if [ "$local_changes" = "true" ]; then - git diff "$commitish" | - inside_as "$name" "$user" sh -exc ' - cd "$1" - if [ "$2" = "true" ]; then - git apply - else - cat > local-changes.diff - fi - ' insert_changes "$dname" "$dirty" - [ "${PIPESTATUS[*]}" = "0 0" ] || { - error "Failed to apply local changes." - return 1 - } - fi - - return 0 -} - -prep() { - # we need some very basic things not present in the container. - # - git - # - tar (CentOS 6 lxc container does not have it) - # - python-argparse (or python3) - local needed="" pair="" pkg="" cmd="" needed="" - for pair in tar:tar git:git; do - pkg=${pair#*:} - cmd=${pair%%:*} - command -v $cmd >/dev/null 2>&1 || needed="${needed} $pkg" - done - if ! command -v python3; then - python -c "import argparse" >/dev/null 2>&1 || - needed="${needed} python-argparse" - fi - needed=${needed# } - if [ -z "$needed" ]; then - error "No prep packages needed" - return 0 +main() { + if [ "$1" = "-h" -o "$1" == "--help" ]; then + Usage 1>&2; + exit 0; fi - error "Installing prep packages: ${needed}" - set -- $needed - local n max r - n=0; max=10; - bcmd="yum install --downloadonly --assumeyes --setopt=keepcache=1" - while n=$(($n+1)); do - error ":: running $bcmd $* [$n/$max]" - $bcmd "$@" - r=$? - [ $r -eq 0 ] && break - [ $n -ge $max ] && { error "gave up on $bcmd"; exit $r; } - nap=$(($n*5)) - error ":: failed [$r] ($n/$max). sleeping $nap." - sleep $nap - done - error ":: running yum install --cacheonly --assumeyes $*" - yum install --cacheonly --assumeyes "$@" -} - -start_container() { - local src="$1" name="$2" - debug 1 "starting container $name from '$src'" - lxc launch "$src" "$name" || { - errorrc "Failed to start container '$name' from '$src'"; + local pt="" mydir=$(dirname "$0") + local run_container="$mydir/run-container" + if [ ! -x "$run_container" ]; then + bad_Usage "Could not find run-container." return - } - CONTAINER=$name - - local out="" ret="" - debug 1 "waiting for networking" - out=$(inside "$name" sh -c ' - i=0 - while [ $i -lt 60 ]; do - getent hosts mirrorlist.centos.org && exit 0 - sleep 2 - done' 2>&1) - ret=$? - if [ $ret -ne 0 ]; then - error "Waiting for network in container '$name' failed. [$ret]" - error "$out" - return $ret - fi - - if [ ! -z "${http_proxy-}" ]; then - debug 1 "configuring proxy ${http_proxy}" - inside "$name" sh -c "echo proxy=$http_proxy >> /etc/yum.conf" - inside "$name" sed -i s/enabled=1/enabled=0/ /etc/yum/pluginconf.d/fastestmirror.conf fi -} - -delete_container() { - debug 1 "removing container $1 [--keep to keep]" - lxc delete --force "$1" -} - -main() { - local short_opts="ahkrsuv" - local long_opts="artifact,dirty,help,keep,rpm,srpm,unittest,verbose" - local getopt_out="" - getopt_out=$(getopt --name "${0##*/}" \ - --options "${short_opts}" --long "${long_opts}" -- "$@") && - eval set -- "${getopt_out}" || - { bad_Usage; return; } - - local cur="" next="" - local artifact="" keep="" rpm="" srpm="" unittest="" version="" - local dirty=false - + + pt=( "$run_container" ) while [ $# -ne 0 ]; do cur="${1:-}"; next="${2:-}"; case "$cur" in - -a|--artifact) artifact=1;; - --dirty) dirty=true;; - -h|--help) Usage ; exit 0;; - -k|--keep) KEEP=true;; - -r|--rpm) rpm=1;; - -s|--srpm) srpm=1;; - -u|--unittest) unittest=1;; - -v|--verbose) VERBOSITY=$((${VERBOSITY}+1));; - --) shift; break;; + -r|--rpm) cur="--package";; + -s|--srpm) cur="--source-package";; + -a|--artifact) cur="--artifacts=.";; + 6|7) cur="centos/$cur";; esac + pt[${#pt[@]}]="$cur" shift; done - - [ $# -eq 1 ] || { bad_Usage "ERROR: Must provide version!"; return; } - version="$1" - case "$version" in - 6|7) :;; - *) error "Expected version of 6 or 7, not '$version'"; return;; - esac - - TEMP_D=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXXXX") || - fail "failed to make tempdir" - trap cleanup EXIT - - # program starts here - local uuid="" name="" user="ci-test" cdir="" - cdir="/home/$user/cloud-init" - uuid=$(uuidgen -t) || { error "no uuidgen"; return 1; } - name="cloud-init-centos-${uuid%%-*}" - - start_container "images:centos/$version" "$name" - - # prep the container (install very basic dependencies) - inside "$name" bash -s prep <"$0" || - { errorrc "Failed to prep container $name"; return; } - - # add the user - inside "$name" useradd "$user" - - debug 1 "inserting cloud-init" - inject_cloud_init "$name" "$user" "$dirty" || { - errorrc "FAIL: injecting cloud-init into $name failed." - return - } - - inside_as_cd "$name" root "$cdir" \ - ./tools/read-dependencies --distro=centos --test-distro || { - errorrc "FAIL: failed to install dependencies with read-dependencies" - return - } - - local errors=0 - inside_as_cd "$name" "$user" "$cdir" \ - sh -ec "git status" || - { errorrc "git checkout failed."; errors=$(($errors+1)); } - - if [ -n "$unittest" ]; then - debug 1 "running unit tests." - inside_as_cd "$name" "$user" "$cdir" \ - nosetests tests/unittests cloudinit || - { errorrc "nosetests failed."; errors=$(($errors+1)); } - fi - - if [ -n "$srpm" ]; then - debug 1 "building srpm." - inside_as_cd "$name" "$user" "$cdir" ./packages/brpm --srpm || - { errorrc "brpm --srpm."; errors=$(($errors+1)); } - fi - - if [ -n "$rpm" ]; then - debug 1 "building rpm." - inside_as_cd "$name" "$user" "$cdir" ./packages/brpm || - { errorrc "brpm failed."; errors=$(($errors+1)); } - fi - - if [ -n "$artifact" ]; then - for built_rpm in $(inside "$name" sh -c "echo $cdir/*.rpm"); do - lxc file pull "$name/$built_rpm" . - done - fi - - if [ "$errors" != "0" ]; then - error "there were $errors errors." - return 1 - fi - return 0 + deprecated + exec "${pt[@]}" } -if [ "${1:-}" = "prep" ]; then - shift - prep "$@" -else - main "$@" -fi +main "$@" + # vi: ts=4 expandtab diff --git a/tools/run-container b/tools/run-container new file mode 100755 index 00000000..499e85b0 --- /dev/null +++ b/tools/run-container @@ -0,0 +1,590 @@ +#!/bin/bash +# This file is part of cloud-init. See LICENSE file for license information. +# +# shellcheck disable=2015,2016,2039,2162,2166 + +set -u + +VERBOSITY=0 +KEEP=false +CONTAINER="" +DEFAULT_WAIT_MAX=30 + +error() { echo "$@" 1>&2; } +fail() { [ $# -eq 0 ] || error "$@"; exit 1; } +errorrc() { local r=$?; error "$@" "ret=$r"; return $r; } + +Usage() { + cat <<EOF +Usage: ${0##*/} [ options ] [images:]image-ref + + This utility can makes it easier to run tests, build rpm and source rpm + generation inside a LXC of the specified version of CentOS. + + To see images available, run 'lxc image list images:' + Example input: + centos/7 + opensuse/42.3 + debian/10 + + options: + -a | --artifacts DIR copy build artifacts out to DIR. + by default artifacts are not copied out. + --dirty apply local changes before running tests. + If not provided, a clean checkout of branch is + tested. Inside container, changes are in + local-changes.diff. + -k | --keep keep container after tests + --pyexe V python version to use. Default=auto. + Should be name of an executable. + ('python2' or 'python3') + -p | --package build a binary package (.deb or .rpm) + -s | --source-package build source package (debuild -S or srpm) + -u | --unittest run unit tests + + Example: + * ${0##*/} --package --source-package --unittest centos/6 +EOF +} + +bad_Usage() { Usage 1>&2; [ $# -eq 0 ] || error "$@"; return 1; } +cleanup() { + if [ -n "$CONTAINER" ]; then + if [ "$KEEP" = "true" ]; then + error "not deleting container '$CONTAINER' due to --keep" + else + delete_container "$CONTAINER" + fi + fi +} + +debug() { + local level=${1}; shift; + [ "${level}" -gt "${VERBOSITY}" ] && return + error "${@}" +} + + +inside_as() { + # inside_as(container_name, user, cmd[, args]) + # executes cmd with args inside container as user in users home dir. + local name="$1" user="$2" + shift 2 + if [ "$user" = "root" ]; then + inside "$name" "$@" + return + fi + local stuffed="" b64="" + stuffed=$(getopt --shell sh --options "" -- -- "$@") + stuffed=${stuffed# -- } + b64=$(printf "%s\n" "$stuffed" | base64 --wrap=0) + inside "$name" su "$user" -c \ + 'cd; eval set -- "$(echo '"$b64"' | base64 --decode)" && exec "$@"'; +} + +inside_as_cd() { + local name="$1" user="$2" dir="$3" + shift 3 + inside_as "$name" "$user" sh -c 'cd "$0" && exec "$@"' "$dir" "$@" +} + +inside() { + local name="$1" + shift + lxc exec "$name" -- "$@" +} + +inject_cloud_init(){ + # take current cloud-init git dir and put it inside $name at + # ~$user/cloud-init. + local name="$1" user="$2" dirty="$3" + local dname="cloud-init" gitdir="" commitish="" + gitdir=$(git rev-parse --git-dir) || { + errorrc "Failed to get git dir in $PWD"; + return + } + local t=${gitdir%/*} + case "$t" in + */worktrees) + if [ -f "${t%worktrees}/config" ]; then + gitdir="${t%worktrees}" + fi + esac + + # attempt to get branch name. + commitish=$(git rev-parse --abbrev-ref HEAD) || { + errorrc "Failed git rev-parse --abbrev-ref HEAD" + return + } + if [ "$commitish" = "HEAD" ]; then + # detached head + commitish=$(git rev-parse HEAD) || { + errorrc "failed git rev-parse HEAD" + return + } + fi + + local local_changes=false + if ! git diff --quiet "$commitish"; then + # there are local changes not committed. + local_changes=true + if [ "$dirty" = "false" ]; then + error "WARNING: You had uncommitted changes. Those changes will " + error "be put into 'local-changes.diff' inside the container. " + error "To test these changes you must pass --dirty." + fi + fi + + debug 1 "collecting ${gitdir} ($dname) into user $user in $name." + tar -C "${gitdir}" -cpf - . | + inside_as "$name" "$user" sh -ec ' + dname=$1 + commitish=$2 + rm -Rf "$dname" + mkdir -p $dname/.git + cd $dname/.git + tar -xpf - + cd .. + git config core.bare false + out=$(git checkout $commitish 2>&1) || + { echo "failed git checkout $commitish: $out" 1>&2; exit 1; } + out=$(git checkout . 2>&1) || + { echo "failed git checkout .: $out" 1>&2; exit 1; } + ' extract "$dname" "$commitish" + [ "${PIPESTATUS[*]}" = "0 0" ] || { + error "Failed to push tarball of '$gitdir' into $name" \ + " for user $user (dname=$dname)" + return 1 + } + + echo "local_changes=$local_changes dirty=$dirty" + if [ "$local_changes" = "true" ]; then + git diff "$commitish" | + inside_as "$name" "$user" sh -exc ' + cd "$1" + if [ "$2" = "true" ]; then + git apply + else + cat > local-changes.diff + fi + ' insert_changes "$dname" "$dirty" + [ "${PIPESTATUS[*]}" = "0 0" ] || { + error "Failed to apply local changes." + return 1 + } + fi + + return 0 +} + +get_os_info_in() { + # prep the container (install very basic dependencies) + [ -n "${OS_VERSION:-}" -a -n "${OS_NAME:-}" ] && return 0 + data=$(run_self_inside "$name" os_info) || + { errorrc "Failed to get os-info in container $name"; return; } + eval "$data" && [ -n "${OS_VERSION:-}" -a -n "${OS_NAME:-}" ] || return + debug 1 "determined $name is $OS_NAME/$OS_VERSION" +} + +os_info() { + get_os_info || return + echo "OS_NAME=$OS_NAME" + echo "OS_VERSION=$OS_VERSION" +} + +get_os_info() { + # run inside container, set OS_NAME, OS_VERSION + # example OS_NAME are centos, debian, opensuse + [ -n "${OS_NAME:-}" -a -n "${OS_VERSION:-}" ] && return 0 + if [ -f /etc/os-release ]; then + OS_NAME=$(sh -c '. /etc/os-release; echo $ID') + OS_VERSION=$(sh -c '. /etc/os-release; echo $VERSION_ID') + if [ -z "$OS_VERSION" ]; then + local pname="" + pname=$(sh -c '. /etc/os-release; echo $PRETTY_NAME') + case "$pname" in + *buster*) OS_VERSION=10;; + *sid*) OS_VERSION="sid";; + esac + fi + elif [ -f /etc/centos-release ]; then + local line="" + read line < /etc/centos-release + case "$line" in + CentOS\ *\ 6.*) OS_VERSION="6"; OS_NAME="centos";; + esac + fi + [ -n "${OS_NAME:-}" -a -n "${OS_VERSION:-}" ] || + { error "Unable to determine OS_NAME/OS_VERSION"; return 1; } +} + +yum_install() { + local n=0 max=10 ret + bcmd="yum install --downloadonly --assumeyes --setopt=keepcache=1" + while n=$((n+1)); do + error ":: running $bcmd $* [$n/$max]" + $bcmd "$@" + ret=$? + [ $ret -eq 0 ] && break + [ $n -ge $max ] && { error "gave up on $bcmd"; exit $ret; } + nap=$((n*5)) + error ":: failed [$ret] ($n/$max). sleeping $nap." + sleep $nap + done + error ":: running yum install --cacheonly --assumeyes $*" + yum install --cacheonly --assumeyes "$@" +} + +zypper_install() { + local pkgs="$*" + set -- zypper --non-interactive --gpg-auto-import-keys install \ + --auto-agree-with-licenses "$@" + debug 1 ":: installing $pkgs with zypper: $*" + "$@" +} + +apt_install() { + apt-get update -q && apt-get install --no-install-recommends "$@" +} + +install_packages() { + get_os_info || return + case "$OS_NAME" in + centos) yum_install "$@";; + opensuse) zypper_install "$@";; + debian|ubuntu) apt_install "$@";; + *) error "Do not know how to install packages on ${OS_NAME}"; + return 1;; + esac +} + +prep() { + # we need some very basic things not present in the container. + # - git + # - tar (CentOS 6 lxc container does not have it) + # - python-argparse (or python3) + local needed="" pair="" pkg="" cmd="" needed="" + local pairs="tar:tar git:git" + local pyexe="$1" + get_os_info + local py2pkg="python2" py3pkg="python3" + case "$OS_NAME" in + opensuse) + py2pkg="python-base" + py3pkg="python3-base";; + esac + + case "$pyexe" in + python2) pairs="$pairs python2:$py2pkg";; + python3) pairs="$pairs python3:$py3pkg";; + esac + + for pair in $pairs; do + pkg=${pair#*:} + cmd=${pair%%:*} + command -v "$cmd" >/dev/null 2>&1 || needed="${needed} $pkg" + done + if [ "$OS_NAME" = "centos" -a "$pyexe" = "python2" ]; then + python -c "import argparse" >/dev/null 2>&1 || + needed="${needed} python-argparse" + fi + needed=${needed# } + if [ -z "$needed" ]; then + error "No prep packages needed" + return 0 + fi + error "Installing prep packages: ${needed}" + # shellcheck disable=SC2086 + set -- $needed + install_packages "$@" +} + +nose() { + local pyexe="$1" cmd="" + shift + get_os_info + if [ "$OS_NAME/$OS_VERSION" = "centos/6" ]; then + cmd="nosetests" + else + cmd="$pyexe -m nose" + fi + ${cmd} "$@" +} + +is_done_cloudinit() { + [ -e "/run/cloud-init/result.json" ] + _RET="" +} + +is_done_systemd() { + local s="" num="$1" + s=$(systemctl is-system-running 2>&1); + _RET="$? $s" + case "$s" in + initializing|starting) return 1;; + *[Ff]ailed*connect*bus*) + # warn if not the first run. + [ "$num" -lt 5 ] || + error "Failed to connect to systemd bus [${_RET%% *}]"; + return 1;; + esac + return 0 +} + +is_done_other() { + local out="" + out=$(getent hosts ubuntu.com 2>&1) + return +} + +wait_inside() { + local name="$1" max="${2:-${DEFAULT_WAIT_MAX}}" debug=${3:-0} + local i=0 check="is_done_other"; + if [ -e /run/systemd ]; then + check=is_done_systemd + elif [ -x /usr/bin/cloud-init ]; then + check=is_done_cloudinit + fi + [ "$debug" != "0" ] && debug 1 "check=$check" + while ! $check $i && i=$((i+1)); do + [ "$i" -ge "$max" ] && exit 1 + [ "$debug" = "0" ] || echo -n . + sleep 1 + done + if [ "$debug" != "0" ]; then + read up _ </proc/uptime + debug 1 "[$name ${i:+done after $i }up=$up${_RET:+ ${_RET}}]" + fi +} + +wait_for_boot() { + local name="$1" + local out="" ret="" wtime=$DEFAULT_WAIT_MAX + get_os_info_in "$name" + [ "$OS_NAME" = "debian" ] && wtime=300 && + debug 1 "on debian we wait for ${wtime}s" + debug 1 "waiting for boot of $name" + run_self_inside "$name" wait_inside "$name" "$wtime" "$VERBOSITY" || + { errorrc "wait inside $name failed."; return; } + + if [ ! -z "${http_proxy-}" ]; then + if [ "$OS_NAME" = "centos" ]; then + debug 1 "configuring proxy ${http_proxy}" + inside "$name" sh -c "echo proxy=$http_proxy >> /etc/yum.conf" + inside "$name" sed -i s/enabled=1/enabled=0/ \ + /etc/yum/pluginconf.d/fastestmirror.conf + else + debug 1 "do not know how to configure proxy on $OS_NAME" + fi + fi +} + +start_container() { + local src="$1" name="$2" + debug 1 "starting container $name from '$src'" + lxc launch "$src" "$name" || { + errorrc "Failed to start container '$name' from '$src'"; + return + } + CONTAINER=$name + wait_for_boot "$name" +} + +delete_container() { + debug 1 "removing container $1 [--keep to keep]" + lxc delete --force "$1" +} + +run_self_inside() { + # run_self_inside(container, args) + local name="$1" + shift + inside "$name" bash -s "$@" <"$0" +} + +run_self_inside_as_cd() { + local name="$1" user="$2" dir="$3" + shift 3 + inside_as_cd "$name" "$user" "$dir" bash -s "$@" <"$0" +} + +main() { + local short_opts="a:hknpsuv" + local long_opts="artifacts:,dirty,help,keep,name:,pyexe:,package,source-package,unittest,verbose" + local getopt_out="" + getopt_out=$(getopt --name "${0##*/}" \ + --options "${short_opts}" --long "${long_opts}" -- "$@") && + eval set -- "${getopt_out}" || + { bad_Usage; return; } + + local cur="" next="" + local package="" source_package="" unittest="" name="" + local dirty=false pyexe="auto" artifact_d="." + + while [ $# -ne 0 ]; do + cur="${1:-}"; next="${2:-}"; + case "$cur" in + -a|--artifacts) artifact_d="$next";; + --dirty) dirty=true;; + -h|--help) Usage ; exit 0;; + -k|--keep) KEEP=true;; + -n|--name) name="$next"; shift;; + --pyexe) pyexe=$next; shift;; + -p|--package) package=1;; + -s|--source-package) source_package=1;; + -u|--unittest) unittest=1;; + -v|--verbose) VERBOSITY=$((VERBOSITY+1));; + --) shift; break;; + esac + shift; + done + + [ $# -eq 1 ] || { bad_Usage "Expected 1 arg, got $# ($*)"; return; } + local img_ref_in="$1" + case "${img_ref_in}" in + *:*) img_ref="${img_ref_in}";; + *) img_ref="images:${img_ref_in}";; + esac + + # program starts here + local out="" user="ci-test" cdir="" home="" + home="/home/$user" + cdir="$home/cloud-init" + if [ -z "$name" ]; then + if out=$(petname 2>&1); then + name="ci-${out}" + elif out=$(uuidgen -t 2>&1); then + name="ci-${out%%-*}" + else + error "Must provide name or have petname or uuidgen" + return 1 + fi + fi + + trap cleanup EXIT + + start_container "$img_ref" "$name" || + { errorrc "Failed to start container for $img_ref"; return; } + + get_os_info_in "$name" || + { errorrc "failed to get os_info in $name"; return; } + + if [ "$pyexe" = "auto" ]; then + case "$OS_NAME/$OS_VERSION" in + centos/*|opensuse/*) pyexe=python2;; + *) pyexe=python3;; + esac + debug 1 "set pyexe=$pyexe for $OS_NAME/$OS_VERSION" + fi + + # prep the container (install very basic dependencies) + run_self_inside "$name" prep "$pyexe" || + { errorrc "Failed to prep container $name"; return; } + + # add the user + inside "$name" useradd "$user" --create-home "--home-dir=$home" || + { errorrc "Failed to add user '$user' in '$name'"; return 1; } + + debug 1 "inserting cloud-init" + inject_cloud_init "$name" "$user" "$dirty" || { + errorrc "FAIL: injecting cloud-init into $name failed." + return + } + + inside_as_cd "$name" root "$cdir" \ + $pyexe ./tools/read-dependencies "--distro=${OS_NAME}" \ + --test-distro || { + errorrc "FAIL: failed to install dependencies with read-dependencies" + return + } + + local errors=( ) + inside_as_cd "$name" "$user" "$cdir" git status || { + errorrc "git checkout failed." + errors[${#errors[@]}]="git checkout"; + } + + if [ -n "$unittest" ]; then + debug 1 "running unit tests." + run_self_inside_as_cd "$name" "$user" "$cdir" nose "$pyexe" \ + tests/unittests cloudinit/ || { + errorrc "nosetests failed."; + errors[${#errors[@]}]="nosetests" + } + fi + + local build_pkg="" build_srcpkg="" pkg_ext="" distflag="" + case "$OS_NAME" in + centos) distflag="--distro=redhat";; + opensuse) distflag="--distro=suse";; + esac + + case "$OS_NAME" in + debian|ubuntu) + build_pkg="./packages/bddeb -d" + build_srcpkg="./packages/bddeb -S -d" + pkg_ext=".deb";; + centos|opensuse) + build_pkg="./packages/brpm $distflag" + build_srcpkg="./packages/brpm $distflag --srpm" + pkg_ext=".rpm";; + esac + if [ -n "$source_package" ]; then + [ -n "$build_pkg" ] || { + error "Unknown package command for $OS_NAME" + return 1 + } + debug 1 "building source package with $build_srcpkg." + # shellcheck disable=SC2086 + inside_as_cd "$name" "$user" "$cdir" $pyexe $build_srcpkg || { + errorrc "failed: $build_srcpkg"; + errors[${#errors[@]}]="source package" + } + fi + + if [ -n "$package" ]; then + [ -n "$build_srcpkg" ] || { + error "Unknown build source command for $OS_NAME" + return 1 + } + debug 1 "building binary package with $build_pkg." + inside_as_cd "$name" "$user" "$cdir" $pyexe $build_pkg || { + errorrc "failed: $build_pkg"; + errors[${#errors[@]}]="binary package" + } + fi + + if [ -n "$artifact_d" ]; then + local art="" + artifact_d="${artifact_d%/}/" + [ -d "${artifact_d}" ] || mkdir -p "$artifact_d" || { + errorrc "failed to create artifact dir '$artifact_d'" + return + } + + for art in $(inside "$name" sh -c "echo $cdir/*${pkg_ext}"); do + lxc file pull "$name/$art" "$artifact_d" || { + errorrc "Failed to pull '$name/$art' to ${artifact_d}" + errors[${#errors[@]}]="artifact copy: $art" + } + debug 1 "wrote ${artifact_d}${art##*/}" + done + fi + + if [ "${#errors[@]}" != "0" ]; then + local e="" + error "there were ${#errors[@]} errors." + for e in "${errors[@]}"; do + error " $e" + done + return 1 + fi + return 0 +} + +case "${1:-}" in + prep|os_info|wait_inside|nose) _n=$1; shift; "$_n" "$@";; + *) main "$@";; +esac + +# vi: ts=4 expandtab |