Ales Komarek | 1b37311 | 2017-08-08 08:48:56 +0200 | [diff] [blame] | 1 | #!/usr/bin/env bash |
| 2 | |
| 3 | # generate and validate reclass-salt-model |
| 4 | # expected to be executed in isolated environment, ie: docker |
| 5 | |
| 6 | if [[ $DEBUG =~ ^(True|true|1|yes)$ ]]; then |
| 7 | set -x |
| 8 | SALT_LOG_LEVEL="--state-verbose=true -ldebug" |
| 9 | fi |
| 10 | |
| 11 | ## Env Options |
| 12 | options() { |
| 13 | export LC_ALL=C |
| 14 | SALT_LOG_LEVEL="--state-verbose=false -lerror" |
| 15 | SALT_OPTS="${SALT_OPTS:- --timeout=120 --state-output=changes --retcode-passthrough --force-color $SALT_LOG_LEVEL }" |
| 16 | RECLASS_ROOT=${RECLASS_ROOT:-/srv/salt/reclass} |
| 17 | BOOTSTRAP_SALTSTACK=${BOOTSTRAP_SALTSTACK:-True} |
| 18 | BOOTSTRAP_SALTSTACK_OPTS=${BOOTSTRAP_SALTSTACK_OPTS:- -dX stable 2016.3 } |
| 19 | SALT_STATE_RETRY=${SALT_STATE_RETRY:-3} |
| 20 | |
| 21 | # try to source local environment & configuration |
| 22 | # shopt -u dotglob |
Petr Michalec | 554c4a3 | 2017-08-08 10:00:03 +0200 | [diff] [blame] | 23 | for path in / . /srv/salt /tmp/kitchen /srv/salt/reclass/classes/cluster /srv/salt/reclass/classes/cluster/${CLUSTER_NAME}; do |
| 24 | export $(find $path -maxdepth 1 -name '*.env' 2> /dev/null | xargs --no-run-if-empty cat ) > /dev/null |
| 25 | done; |
Ales Komarek | 1b37311 | 2017-08-08 08:48:56 +0200 | [diff] [blame] | 26 | |
| 27 | export MAGENTA='\033[0;95m' |
| 28 | export YELLOW='\033[1;33m' |
| 29 | export BLUE='\033[0;35m' |
| 30 | export CYAN='\033[0;96m' |
| 31 | export RED='\033[0;31m' |
| 32 | export NC='\033[0m' # No Color' |
| 33 | } |
| 34 | |
| 35 | ## Functions |
| 36 | log_info() { |
| 37 | echo -e "${YELLOW}[INFO] $* ${NC}" |
| 38 | } |
| 39 | |
| 40 | log_warn() { |
| 41 | echo -e "${MAGENTA}[WARN] $* ${NC}" |
| 42 | } |
| 43 | |
| 44 | log_err() { |
| 45 | echo -e "${RED}[ERROR] $* ${NC}" >&2 |
| 46 | } |
| 47 | |
| 48 | _atexit() { |
| 49 | RETVAL=$? |
| 50 | trap true INT TERM EXIT |
| 51 | |
| 52 | if [ $RETVAL -ne 0 ]; then |
| 53 | log_err "Execution failed" |
| 54 | else |
| 55 | log_info "Execution successful" |
| 56 | fi |
| 57 | return $RETVAL |
| 58 | } |
| 59 | |
| 60 | retry() { |
| 61 | local tries |
| 62 | if [[ $1 =~ ^[0-9]+$ ]]; then |
| 63 | tries=$1; shift |
| 64 | else |
| 65 | tries=3 |
| 66 | fi |
| 67 | for i in $(seq 1 $tries); do |
| 68 | "$@" && return 0 || sleep $i |
| 69 | done |
| 70 | return 1 |
| 71 | } |
| 72 | |
| 73 | ## Main |
| 74 | |
| 75 | system_config() { |
| 76 | log_info "System configuration" |
| 77 | |
| 78 | # salt-formulas custom modules dependencies, etc: |
| 79 | $SUDO apt install -qqq -y iproute2 curl sudo apt-transport-https python-psutil python-apt python-m2crypto python-oauth python-pip &>/dev/null |
| 80 | |
| 81 | $SUDO mkdir -p $RECLASS_ROOT/classes/service |
| 82 | $SUDO mkdir -p /root/.ssh |
| 83 | echo -e "Host *\n\tStrictHostKeyChecking no\n" | $SUDO tee ~/.ssh/config >/dev/null |
| 84 | echo -e "Host *\n\tStrictHostKeyChecking no\n" | $SUDO tee /root/.ssh/config >/dev/null |
| 85 | echo "127.0.1.2 salt" | $SUDO tee -a /etc/hosts >/dev/null |
| 86 | |
| 87 | if [[ $BOOTSTRAP_SALTSTACK =~ ^(True|true|1|yes)$ ]]; then |
| 88 | curl -L https://bootstrap.saltstack.com | $SUDO sh -s -- -M ${BOOTSTRAP_SALTSTACK_OPTS} &>/dev/null || true |
| 89 | fi |
| 90 | |
| 91 | which reclass || $SUDO apt install -qqq -y reclass |
| 92 | |
| 93 | which reclass-salt || { |
| 94 | test -e /usr/share/reclass/reclass-salt && { |
| 95 | ln -fs /usr/share/reclass/reclass-salt /usr/bin |
| 96 | } |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | |
| 101 | saltmaster_bootstrap() { |
| 102 | |
| 103 | log_info "Salt master, minion setup (salt-master-setup.sh)" |
| 104 | test -n "$MASTER_HOSTNAME" || exit 1 |
| 105 | |
| 106 | pgrep salt-master | sed /$$/d | xargs --no-run-if-empty -i{} $SUDO kill -9 {} |
| 107 | pkill -9 salt-minion |
| 108 | SCRIPTS=$(dirname $0) |
| 109 | test -e ${SCRIPTS}/salt-master-setup.sh || \ |
Petr Michalec | 5efab50 | 2017-08-08 14:10:51 +0200 | [diff] [blame] | 110 | curl -sL "https://raw.githubusercontent.com/salt-formulas/salt-formulas-scripts/master/salt-master-setup.sh" |$SUDO tee ${SCRIPTS}/salt-master-setup.sh > /dev/null; |
Ales Komarek | 1b37311 | 2017-08-08 08:48:56 +0200 | [diff] [blame] | 111 | $SUDO chmod +x *.sh; |
| 112 | test -e ${SCRIPTS}/.salt-master-setup.sh.passed || { |
| 113 | export SALT_MASTER=localhost |
| 114 | export MINION_ID=${MASTER_HOSTNAME} |
| 115 | if ! [[ $DEBUG =~ ^(True|true|1|yes)$ ]]; then |
| 116 | SALT_MASTER_SETUP_OUTPUT='/dev/stdout' |
| 117 | fi |
| 118 | #if ! $SUDO ${SCRIPTS}/salt-master-setup.sh master &> ${SALT_MASTER_SETUP_OUTPUT:-/tmp/salt-master-setup.log}; then |
| 119 | if ! $SUDO ${SCRIPTS}/salt-master-setup.sh master; then |
| 120 | #cat /tmp/salt-master-setup.log |
| 121 | log_err "salt-master-setup.sh failed." |
| 122 | exit 1 |
| 123 | else |
| 124 | $SUDO touch ${SCRIPTS}/.salt-master-setup.sh.passed |
| 125 | fi |
| 126 | } |
| 127 | |
| 128 | log_info "Clean up generated" |
| 129 | cd $RECLASS_ROOT |
| 130 | $SUDO rm -rf $RECLASS_ROOT/nodes/_generated/* |
| 131 | |
| 132 | log_info "Re/starting salt services" |
| 133 | pgrep salt-master | sed /$$/d | xargs --no-run-if-empty -i{} $SUDO kill -9 {} |
| 134 | pkill -9 salt-minion |
| 135 | sleep 1 |
| 136 | $SUDO service salt-master restart |
| 137 | $SUDO service salt-minion restart |
| 138 | sleep 10 |
| 139 | } |
| 140 | |
| 141 | # Init salt master |
| 142 | saltmaster_init() { |
| 143 | |
| 144 | log_info "Runing saltmaster states" |
| 145 | test -n "$MASTER_HOSTNAME" || exit 1 |
| 146 | |
| 147 | set -e |
| 148 | $SUDO salt-call saltutil.sync_all >/dev/null |
| 149 | |
| 150 | # TODO: Placeholder update saltmaster spec (nodes/FQDN.yml) to be able to bootstrap with minimal configuration |
| 151 | # (ie: with linux, git, salt formulas) |
| 152 | |
| 153 | #log_info "Verify SaltMaster, before salt-master is fully initialized" |
| 154 | #if ! $SUDO reclass-salt -p ${MASTER_HOSTNAME} &> /tmp/${MASTER_HOSTNAME}.pillar;then |
| 155 | # log_warn "Node verification before initialization failed."; cat /tmp/${MASTER_HOSTNAME}.pillar; |
| 156 | #fi |
| 157 | |
| 158 | log_info "State: salt.master.env" |
| 159 | if ! $SUDO salt-call ${SALT_OPTS} -linfo state.apply salt.master.env; then |
| 160 | log_err "State salt.master.env failed, keep your eyes wide open." |
| 161 | fi |
| 162 | |
| 163 | log_info "State: salt.master.pillar" |
| 164 | retry ${SALT_STATE_RETRY} $SUDO salt-call ${SALT_OPTS} state.apply salt.master.pillar pillar='{"reclass":{"storage":{"data_source":{"engine":"local"}}}}' |
| 165 | # Note: sikp reclass data dir states |
| 166 | # in order to avoid pull from configured repo/branch |
| 167 | |
| 168 | # Revert temporary SaltMaster minimal configuration, if any |
| 169 | pushd $RECLASS_ROOT |
| 170 | if [ $(git diff --name-only nodes | sort | uniq | wc -l) -ge 1 ]; then |
| 171 | git status || true |
| 172 | log_warn "Locally modified $RECLASS_ROOT/nodes found. (Possibly salt-master minimized setup from salt-master-setup.sh call)" |
| 173 | log_info "Checkout HEAD state of $RECLASS_ROOT/nodes/*." |
| 174 | git checkout -- $RECLASS_ROOT/nodes || true |
| 175 | log_info "Re-Run states: salt.master.env and salt.master.pillar according the HEAD state." |
| 176 | log_info "State: salt.master.env" |
| 177 | if ! $SUDO salt-call ${SALT_OPTS} -linfo state.apply salt.master.env; then |
| 178 | log_err "State salt.master.env failed, keep your eyes wide open." |
| 179 | fi |
| 180 | log_info "State: salt.master.pillar" |
| 181 | retry ${SALT_STATE_RETRY} $SUDO salt-call ${SALT_OPTS} state.apply salt.master.pillar pillar='{"reclass":{"storage":{"data_source":{"engine":"local"}}}}' |
| 182 | fi |
| 183 | popd |
| 184 | |
| 185 | log_info "State: salt.master.storage.node" |
| 186 | set +e |
| 187 | $SUDO salt-call ${SALT_OPTS} state.apply reclass.storage.node |
| 188 | ret=$? |
| 189 | set -e |
| 190 | |
| 191 | if [[ $ret -eq 2 ]]; then |
| 192 | log_err "State reclass.storage.node failed with exit code 2 but continuing." |
| 193 | elif [[ $ret -ne 0 ]]; then |
| 194 | log_err "State reclass.storage.node failed with exit code $ret" |
| 195 | exit 1 |
| 196 | fi |
| 197 | |
| 198 | log_info "Re/starting salt services" |
| 199 | $SUDO sed -i 's/^master:.*/master: localhost/' /etc/salt/minion.d/minion.conf |
| 200 | $SUDO service salt-minion restart >/dev/null |
| 201 | $SUDO salt-call ${SALT_OPTS} saltutil.sync_all >/dev/null |
| 202 | |
| 203 | verify_salt_master |
| 204 | set +e |
| 205 | |
| 206 | } |
| 207 | |
| 208 | |
| 209 | function verify_salt_master() { |
| 210 | set -e |
| 211 | |
| 212 | log_info "Verify Salt master" |
| 213 | test -n "$MASTER_HOSTNAME" || exit 1 |
| 214 | |
| 215 | if [[ $VERIFY_SALT_CALL =~ ^(True|true|1|yes)$ ]]; then |
| 216 | $SUDO salt-call ${SALT_OPTS} --id=${MASTER_HOSTNAME} grains.item roles > /tmp/${MASTER_HOSTNAME}.grains.item.roles |
| 217 | $SUDO salt-call ${SALT_OPTS} --id=${MASTER_HOSTNAME} state.show_lowstate > /tmp/${MASTER_HOSTNAME}.state.show_state |
| 218 | $SUDO salt-call --no-color grains.items |
| 219 | $SUDO salt-call --no-color pillar.data |
| 220 | fi |
| 221 | if ! $SUDO reclass --nodeinfo ${MASTER_HOSTNAME} > /tmp/${MASTER_HOSTNAME}.reclass.nodeinfo; then |
| 222 | log_err "For more details see full log /tmp/${MASTER_HOSTNAME}.reclass.nodeinfo" |
| 223 | exit 1 |
| 224 | fi |
| 225 | } |
| 226 | |
| 227 | function verify_salt_minion() { |
| 228 | set -e |
| 229 | node=$1 |
| 230 | log_info "Verifying ${node}" |
| 231 | if [[ $VERIFY_SALT_CALL =~ ^(True|true|1|yes)$ ]]; then |
| 232 | $SUDO salt-call ${SALT_OPTS} --id=${node} grains.item roles > /tmp/${node}.grains.item.roles |
| 233 | $SUDO salt-call ${SALT_OPTS} --id=${node} state.show_lowstate > /tmp/${node}.state.show_lowstate |
| 234 | fi |
| 235 | if ! $SUDO reclass --nodeinfo ${node} > /tmp/${node}.reclass.nodeinfo; then |
| 236 | log_err "For more details see full log /tmp/${node}.reclass.nodeinfo" |
| 237 | if [[ ${BREAK_ON_VERIFICATION_ERROR:-yes} =~ ^(True|true|1|yes)$ ]]; then |
| 238 | exit 1 |
| 239 | fi |
| 240 | fi |
| 241 | } |
| 242 | |
| 243 | function verify_salt_minions() { |
| 244 | #set -e |
| 245 | NODES=$(find $RECLASS_ROOT/nodes/ -name "*.yml" | grep -v "cfg") |
| 246 | log_info "Verifying minions: $(echo ${NODES}|xargs)" |
| 247 | |
| 248 | # Parallel |
| 249 | #echo $NODES | parallel --no-notice -j 2 --halt 2 "verify_salt_minion \$(basename {} .yml) > {}.pillar_verify" |
| 250 | #ls -lrta *.pillar_verify | tail -n 1 | xargs -n1 tail -n30 |
| 251 | |
| 252 | function filterFails() { |
| 253 | grep -v '/grains' | tee -a $1 | tail -n20 |
| 254 | } |
| 255 | |
| 256 | log_info "Verify nodes" |
| 257 | passed=0 |
| 258 | for node in ${NODES}; do |
| 259 | node=$(basename $node .yml) |
| 260 | |
| 261 | # filter first in cluster.. ctl-01, mon-01, etc.. |
| 262 | if [[ "${node//.*}" =~ 01 || "${node//.*}" =~ 02 ]] ;then |
| 263 | verify_salt_minion ${node} || continue |
| 264 | else |
| 265 | echo Skipped $node. |
| 266 | fi |
| 267 | passed=$(($passed+1)) |
| 268 | done |
| 269 | # fail on failures |
| 270 | total=$(echo $NODES | xargs --no-run-if-empty -n1 echo |wc -l) |
| 271 | test ! $passed -lt $total || log_err "Results: $passed of $total passed." |
| 272 | test ! $passed -lt $total || { |
| 273 | tail -n50 /tmp/*.pillar_verify |
| 274 | return 1 |
| 275 | } |
| 276 | } |
| 277 | |
| 278 | |
| 279 | options |
| 280 | # detect if file is being sourced |
| 281 | [[ "$0" != "$BASH_SOURCE" ]] || { |
| 282 | log_info "Bootstrap & verification of SaltMaster and configured minions." |
| 283 | trap _atexit INT TERM EXIT |
| 284 | system_config |
| 285 | |
| 286 | saltmaster_bootstrap &&\ |
| 287 | saltmaster_init &&\ |
| 288 | |
| 289 | verify_salt_minions |
| 290 | } |