#!/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; }

Usage() {
    cat <<EOF
Usage: ${0##*/} [ options ] version

    This utility can makes it easier to run tests, build rpm and source rpm
        generation inside a LXC of the specified version of CentOS.

    version is major release number (6 or 7)

    options:
      -a | --artifact keep .rpm artifacts
      -k | --keep     keep container after tests
      -r | --rpm      build .rpm
      -s | --srpm     build .src.rpm
      -u | --unittest run unit tests

    Example:
      * ${0##*/} --rpm --srpm --unittest 6
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" top_d="" dname="" pstat=""
    top_d=$(git rev-parse --show-toplevel) || {
        errorrc "Failed to get git top level in $PWD";
        return
    }
    dname=$(basename "${top_d}") || return
    debug 1 "collecting ${top_d} ($dname) into user $user in $name."
    tar -C "${top_d}/.." -cpf - "$dname" |
        inside_as "$name" "$user" sh -ec '
            dname=$1
            rm -Rf "$dname"
            tar -xpf -
            [ "$dname" = "cloud-init" ] || mv "$dname" cloud-init' \
            extract "$dname"
    [ "${PIPESTATUS[*]}" = "0 0" ] || {
        error "Failed to push tarball of '$top_d' into $name" \
            " for user $user (dname=$dname)"
        return 1
    }
    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
    fi
    error "Installing prep packages: ${needed}"
    yum install --assumeyes ${needed}
}

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

    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"
    fi
}

delete_container() {
    debug 1 "removing container $1 [--keep to keep]"
    lxc delete --force "$1"
}

main() {
    local short_opts="ahkrsuv"
    local long_opts="artifact,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=""

    while [ $# -ne 0 ]; do
        cur="${1:-}"; next="${2:-}";
        case "$cur" in
            -a|--artifact) artifact=1;;
            -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;;
        esac
        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" || {
        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 checkout .; 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 ||
            { 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
}

if [ "$1" = "prep" ]; then
    shift
    prep "$@"
else
    main "$@"
fi
# vi: ts=4 expandtab