| #!/usr/bin/env bash |
| |
| set -e |
| if [[ $DEBUG =~ ^(True|true|1|yes)$ ]]; then |
| set -x |
| fi |
| |
| ## Overrideable options |
| DOCKER_IMAGE=${DOCKER_IMAGE:-"ubuntu:16.04"} |
| DIST=${DIST:-xenial} |
| RECLASS_ROOT=${RECLASS_ROOT:-$(pwd)} |
| SALT_OPTS="${SALT_OPTS} --retcode-passthrough --force-color" |
| DOCKER_OPTS="${DOCKER_OPTS} -e DEBIAN_FRONTEND=noninteractive" |
| SKIP_CLEANUP=${SKIP_CLEANUP:-0} |
| USER_ID=$(id -u) |
| GROUP_ID=$(id -g) |
| |
| declare -a CONTAINERS |
| |
| ## Functions |
| log_info() { |
| echo "[INFO] $*" |
| } |
| |
| log_err() { |
| echo "[ERROR] $*" >&2 |
| } |
| |
| docker_exec() { |
| if [[ $DETACH =~ ^(True|true|1|yes)$ ]]; then |
| exec_opts="-d" |
| else |
| exec_opts="" |
| fi |
| |
| docker exec ${exec_opts} ${CONTAINER} /bin/bash -c "$*" |
| } |
| |
| _atexit() { |
| RETVAL=$? |
| trap true INT TERM EXIT |
| if [ $SKIP_CLEANUP -eq 1 ]; then |
| return $RETVAL |
| fi |
| |
| log_info "Cleaning up" |
| |
| for container in ${CONTAINERS[@]}; do |
| CONTAINER=$container docker_exec "chown -R ${USER_ID}:${GROUP_ID} /srv/salt/reclass" || true |
| docker rm -f $container >/dev/null || true |
| done |
| |
| if [ $RETVAL -ne 0 ]; then |
| log_err "Execution failed" |
| else |
| log_info "Execution successful" |
| fi |
| |
| return $RETVAL |
| } |
| |
| run_container() { |
| MASTER_HOSTNAME=$1 |
| CONTAINER=$(docker run ${DOCKER_OPTS} --name ${MASTER_HOSTNAME}-$(date +%s) -h $(echo ${MASTER_HOSTNAME}|cut -d . -f 1) -v ${RECLASS_ROOT}:/srv/salt/reclass -i -t -d ${DOCKER_IMAGE}) |
| echo $CONTAINER |
| } |
| |
| test_master() { |
| local MASTER_HOSTNAME=$1 CLUSTER_DOMAIN |
| CLUSTER_DOMAIN=${MASTER_HOSTNAME#*.} |
| log_info "Installing packages" |
| docker_exec "which wget >/dev/null || (apt-get update; apt-get install -y wget)" |
| docker_exec "echo 'deb [arch=amd64] http://apt-mk.mirantis.com/${DIST}/ nightly salt salt-latest' > /etc/apt/sources.list.d/apt-mk.list" |
| docker_exec "wget -O - http://apt-mk.mirantis.com/public.gpg | apt-key add -" |
| |
| docker_exec "apt-get update" |
| docker_exec "apt-get install -y salt-master python-psutil iproute2 curl python-dev python-pip reclass salt-formula-*" |
| # FIXME: Use master reclass to debug undefined vars. Remove when reclass is updated |
| docker_exec "pip install -U https://github.com/madduck/reclass/archive/master.zip" |
| |
| log_info "Setting up Salt master" |
| # TODO: remove grains.d hack when fixed in formula |
| docker_exec "mkdir -p /etc/salt/grains.d && touch /etc/salt/grains.d/dummy" |
| docker_exec "[ ! -d /etc/salt/pki/minion ] && mkdir -p /etc/salt/pki/minion" |
| docker_exec "[ ! -d /etc/salt/master.d ] && mkdir -p /etc/salt/master.d || true" |
| docker_exec "cat << 'EOF' >> /etc/salt/master.d/master.conf |
| file_roots: |
| base: |
| - /usr/share/salt-formulas/env |
| pillar_opts: False |
| open_mode: True |
| reclass: &reclass |
| storage_type: yaml_fs |
| inventory_base_uri: /srv/salt/reclass |
| ext_pillar: |
| - reclass: *reclass |
| master_tops: |
| reclass: *reclass |
| EOF" |
| |
| log_info "Setting up reclass" |
| docker_exec "[ -d /srv/salt/reclass/classes/service ] || mkdir -p /srv/salt/reclass/classes/service || true" |
| docker_exec "for i in /usr/share/salt-formulas/reclass/service/*; do |
| [ -e /srv/salt/reclass/classes/service/\$(basename \$i) ] || ln -s \$i /srv/salt/reclass/classes/service/\$(basename \$i) |
| done" |
| docker_exec "chown -R ${USER_ID}:${GROUP_ID} /srv/salt/reclass/classes/service" |
| docker_exec "[ ! -d /etc/reclass ] && mkdir /etc/reclass || true" |
| docker_exec "cat << 'EOF' >> /etc/reclass/reclass-config.yml |
| storage_type: yaml_fs |
| pretty_print: True |
| output: yaml |
| inventory_base_uri: /srv/salt/reclass |
| EOF" |
| |
| log_info "Setting up Salt minion" |
| docker_exec "apt-get install -y salt-minion" |
| docker_exec "[ ! -d /etc/salt/minion.d ] && mkdir -p /etc/salt/minion.d || true" |
| docker_exec "cat << EOF >> /etc/salt/minion.d/minion.conf |
| id: ${MASTER_HOSTNAME} |
| master: localhost |
| EOF" |
| |
| log_info "Starting Salt master service" |
| DETACH=1 docker_exec "/usr/bin/salt-master" |
| sleep 3 |
| |
| docker_exec "salt-call saltutil.sync_all" |
| log_info "Running states to finish Salt master setup" |
| docker_exec "reclass --nodeinfo ${MASTER_HOSTNAME} >/dev/null" |
| docker_exec "salt-call ${SALT_OPTS} state.show_top" |
| |
| if [[ $SALT_MASTER_FULL =~ ^(True|true|1|yes)$ ]]; then |
| # TODO: can fail on "hostname: you must be root to change the host name" |
| docker_exec "salt-call ${SALT_OPTS} state.sls linux,openssh" || true |
| docker_exec "salt-call ${SALT_OPTS} state.sls salt,reclass" |
| else |
| docker_exec "salt-call ${SALT_OPTS} state.sls reclass.storage.node" || true |
| fi |
| |
| NODES=$(docker_exec "find /srv/salt/reclass/nodes -type f -name *${CLUSTER_DOMAIN}.yml ! -name cfg*") |
| for node in ${NODES}; do |
| node=$(basename $node .yml) |
| log_info "Testing node ${node}" |
| docker_exec "reclass --nodeinfo ${node} >/dev/null" |
| docker_exec "salt-call ${SALT_OPTS} --id=${node} state.show_top" |
| docker_exec "salt-call ${SALT_OPTS} --id=${node} state.show_lowstate >/dev/null" |
| done |
| } |
| |
| run_tests() { |
| local master=$1 |
| |
| log_info "Testing Salt master ${master}" |
| log_info "Creating docker container from image ${DOCKER_IMAGE}" |
| CONTAINER=$(run_container $master) |
| CONTAINERS+=(${CONTAINER}) |
| test_master $master |
| } |
| |
| find_all_and_run_tests() { |
| local masters |
| |
| masters=$(find nodes -type f -name cfg*.yml) |
| for master in ${masters[@]}; do |
| master=$(basename $master .yml) |
| echo "$master" |
| run_tests $master |
| done |
| } |
| |
| ## Main |
| trap _atexit INT TERM EXIT |
| |
| case $1 in |
| "" | all) |
| find_all_and_run_tests |
| ;; |
| *) |
| for master; do |
| run_tests $master |
| done |
| ;; |
| esac |