blob: 343324c9ed5d94fa0f662a5f099e380e667730f7 [file] [log] [blame]
Ales Komarek1b373112017-08-08 08:48:56 +02001#!/usr/bin/env bash
2
3# generate and validate reclass-salt-model
4# expected to be executed in isolated environment, ie: docker
5
6if [[ $DEBUG =~ ^(True|true|1|yes)$ ]]; then
7 set -x
8 SALT_LOG_LEVEL="--state-verbose=true -ldebug"
9fi
10
11## Env Options
12options() {
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 Michalec554c4a32017-08-08 10:00:03 +020023 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 Komarek1b373112017-08-08 08:48:56 +020026
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
36log_info() {
37 echo -e "${YELLOW}[INFO] $* ${NC}"
38}
39
40log_warn() {
41 echo -e "${MAGENTA}[WARN] $* ${NC}"
42}
43
44log_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
60retry() {
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
75system_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
101saltmaster_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 Michalec5efab502017-08-08 14:10:51 +0200110 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 Komarek1b373112017-08-08 08:48:56 +0200111 $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
142saltmaster_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
209function 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
227function 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
243function 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
279options
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}