-
Notifications
You must be signed in to change notification settings - Fork 4
/
deploy.sh
executable file
·349 lines (298 loc) · 8.26 KB
/
deploy.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
#!/bin/bash
# nullglob is required for globbing release-list (if no releases exists)
shopt -s nullglob
# check if stdout is a terminal...
if [ -t 1 ]; then
# see if it supports colors...
ncolors=$(tput colors)
if test -n "$ncolors" && test $ncolors -ge 8; then
bold="$(tput bold)"
underline="$(tput smul)"
standout="$(tput smso)"
normal="$(tput sgr0)"
black="$(tput setaf 0)"
red="$(tput setaf 1)"
green="$(tput setaf 2)"
yellow="$(tput setaf 3)"
blue="$(tput setaf 4)"
magenta="$(tput setaf 5)"
cyan="$(tput setaf 6)"
white="$(tput setaf 7)"
fi
fi
errstar="${bold}${red} * ${normal}"
infostar="${bold}${green} * ${normal}"
LOCK_FILE=.$(basename $1 .sh).lock
################ Functions ###########################
die(){
echo
echo "${errstar} ERROR: during stage ${stage}:"
echo "${errstar} $*"
if ! rm $LOCK_FILE; then
echo "${errstar}"
echo "${errstar} Failed to delete lock file."
fi
local frame=0
set -o pipefail
echo "${errstar}"
echo "${errstar} Callstack:"
while caller $frame | awk -v errstar="${errstar}" '{print errstar " "$3":"$1" in "$2}'; do
((frame++));
done
echo "${errstar}"
echo "${errstar} Variables:"
echo "${errstar} VCS: ${VCS}"
echo "${errstar} RELEASE_WC: ${RELEASE_WC}"
echo "${errstar} EXPORT_TARGET: ${EXPORT_TARGET}"
echo "${errstar} SYMLINK_PATH: ${SYMLINK_PATH}"
echo "${errstar} DST: ${DST}"
exit 1
}
phase_start(){
local desc=""
if [[ $# -gt 0 ]]; then
desc=": $*"
fi
echo "${infostar} Running \"${stage}\"${desc}"
}
phase_end(){
echo "${infostar} .. OK"
}
update_pre_hook(){
return 0
}
update_repo_cmd() {
case $VCS in
svn)
echo "svn up \"$RELEASE_WC\""
;;
git)
echo "(cd \"$RELEASE_WC\"; git pull)"
;;
esac
}
update_post_hook(){
return 0
}
do_update(){
stage="update"
phase_start
update_pre_hook || die "update_pre_hook returned non-zero status, aborting"
eval `update_repo_cmd` || die "Unable to do update local repository"
update_post_hook || die "update_post_hook returned non-zero status, aborting"
phase_end
}
export_pre_hook(){
return 0
}
export_repo_cmd() {
case $VCS in
svn)
echo "svn export \"$RELEASE_WC\" \"${DST}\""
;;
git)
# Until git 1.8 (where git export-index support submodules) must use rsync with --exclude='.git'
# echo "(cd \"$RELEASE_WC/\"; git checkout-index -a -f --prefix=\"$EXPORT_TARGET/$releasename\" )"
echo "rsync -av --exclude='.git' \"$RELEASE_WC/\" \"${DST}\""
;;
esac
}
export_post_hook(){
file_prune
return $?
}
do_export(){
stage="export"
phase_start
export_pre_hook || die "export_pre_hook returned non-zero status, aborting"
eval `export_repo_cmd` || die "Unable to create export"
export_post_hook || die "export_post_hook returned non-zero status, aborting"
phase_end
}
file_prune(){
if [[ ${stage} != "export" ]]; then
die "file_prune called from stage \"${stage}\", not \"export\" as it should"
fi
if [[ ${PRUNE:+1} ]]; then
for pattern in "${PRUNE[@]}"; do
find "${EXPORT_TARGET}/$releasename" -regex "${pattern}" -delete -printf "%p removed\n"
done
fi
}
symlink_pre_hook(){
return 0
}
symlink_update() {
local src=$1
local dst=$2
# create new symlink
ln -s $src ${dst}_tmp || die "Unable to create new symlink"
# replace old symlink
mv -Tf ${dst}_tmp $dst || die "Unable to replace production symlink"
}
symlink_post_hook(){
return 0
}
do_symlink(){
stage="symlink"
phase_start
symlink_pre_hook || die "symlin_pre_hook returned non-zero status, aborting"
symlink_update $DST $SYMLINK_PATH || die
symlink_post_hook || die "symlin_post_hook returned non-zero status, aborting"
phase_end
}
# Delete old releases
do_prune(){
stage="prune"
phase_start "Checking for old releases to remove"
for dir in ${prune[@]}; do
rm -rf $dir || die
done
phase_end
}
# Called when script finishes or if user aborts
cleanup(){
rm $LOCK_FILE || die
}
handle_sigint(){
cleanup
echo -e "\n${errstar} Deploy aborted."
exit 1
}
# This prints the path verbatim if it begins with a /.
# If not it must be a relative path, so it prepends
# $PWD to the front.
# The #./ part strips off ./ from the front of $1.
realpath() {
[[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"
}
############## END FUNCTIONS ####################3
# Catch ctrl-c
trap handle_sigint SIGINT
if [ $# -gt 1 ]; then
echo "Bad usage"
exit 48
fi
if [ $# -eq 1 ]; then
SETTINGS_FILE=$1
else
SETTINGS_FILE=$(dirname "$0")/deploy_settings.sh
fi
if [ ! -e $SETTINGS_FILE ]; then
echo "${errstar} Cannot find settings file. Expected location: $SETTINGS_FILE"
exit 1
fi
SETTINGS_FILE_OK=0
source "$SETTINGS_FILE"
if [ $SETTINGS_FILE_OK -eq 1 ]; then
echo "${infostar} Settings file OK"
else
echo "${errstar}Unable to parse settings file $SETTINGS_FILE" >&2
exit 1
fi
# normalize some important paths
LOCK_FILE=$(realpath "$LOCK_FILE")
SYMLINK_PATH=$(realpath "$SYMLINK_PATH")
RELEASE_WC=$(realpath "$RELEASE_WC")
EXPORT_TARGET=$(realpath "$EXPORT_TARGET")
# Move to correct working directory if script is called from another location to
# fix relative paths
DIR=$(realpath $(dirname "${SETTINGS_FILE}"))
echo "${infostar} Working directory: ${DIR}"
cd ${DIR}
# Create release name
releasename="${releaseprefix:-release}-`date +%Y%m%d%H%M%S`"
DST="${EXPORT_TARGET}/${releasename}"
# Check that release path exists
if [ ! -e "${EXPORT_TARGET}" ]; then
echo "${errstar} Export target $EXPORT_TARGET does not seem to exist. Aborting."
exit 1
fi
# abort if the working copy doesn't exist
if [ ! -e ${RELEASE_WC} ]; then
echo "${errstar} Working copy '${RELEASE_WC}\` does not exist"
exit 1
fi
# Check vcs that we support selected vcs:
case $VCS in
svn)
echo "${infostar} Using SVN"
if [ ! -e ${RELEASE_WC}/.svn ]; then
echo "${errstar} Export target ${RELEASE_WC} is not a subversion repository. Aborting."
exit 1
fi
;;
git)
echo "${infostar} Using GIT (${RELEASE_WC})"
if [ ! -e ${RELEASE_WC}/.git ]; then
echo "${errstar} Export target ${RELEASE_WC} is not a git repository. Aborting."
echo "${errstar} You should initialize the folder by cloning the repository of your choice and checkout the correct branch, e.g:"
echo "${errstar} 1. git clone git@example.net:REPO \"${RELEASE_WC}\""
echo "${errstar} 2. cd \"${RELEASE_WC}\""
echo "${errstar} 3. git checkout BRANCH"
exit 1
fi
;;
*)
echo "${errstar} Unknown vcs specifed: $VCS. Aborting."
exit 1
esac
# abort if SYMLINK_PATH exists but is not a symlink
if [[ ( -e ${SYMLINK_PATH} ) && (! -L ${SYMLINK_PATH}) ]]; then
echo "${errstar} SYMLINK_PATH already exists but it not a symlink. Check configuration and/or remove ${SYMLINK_PATH} manually."
exit 1
elif [[ (-L ${SYMLINK_PATH}) && (! -e ${SYMLINK_PATH}) ]]; then
echo "${errstar} Warning: ${SYMLINK_PATH} is a dangling symlink."
fi
# Create list of old releases to an array called "prune"
releases=($EXPORT_TARGET/${prefix:-release}-* $EXPORT_TARGET/$releasename)
prune=()
while [[ ${#releases[*]} -gt ${RELEASE_COUNT:-8} ]]; do
# pop first element from array, store it in "prune"
prune+=(${releases[0]})
releases=(${releases[@]:1})
done
# Check for lock file
if [ -e $LOCK_FILE ]; then
echo "${errstar} Lock file \"$LOCK_FILE\" exists, bailing out." >&2
exit 1
fi
# Ask user to confirm
echo
echo "*********************************"
echo "This is what I'll do (excluding hooks):"
echo " touch $LOCK_FILE"
echo -n " "; update_repo_cmd
echo -n " "; export_repo_cmd
echo " ln -s $DST ${SYMLINK_PATH}_tmp"
echo " mv -Tf ${DST}_tmp ${SYMLINK_PATH}"
for dir in ${prune[@]}; do
echo " rm -rf $dir"
done
if [[ ${PRUNE:+1} ]]; then
for pattern in "${PRUNE[@]}"; do
echo " find \"${DST}\" -regex \"${pattern}\" -delete"
done
fi
echo " rm $LOCK_FILE"
echo "*********************************"
echo
answer="fail"
until [[ "$answer" = "yes" ]]; do
echo -n "${bold}Do you wish to continue?${normal} [${green}yes${normal}/${red}no${normal}] "
read answer
if [[ "$answer" = "no" ]]; then
echo "Aborting."
exit 45
fi
done
# Create lock file
touch $LOCK_FILE || die "Unable to create lock file"
stage=none
do_update
do_export
do_symlink
do_prune
# remove lock and restore cwd
cleanup
echo "${infostar} Deploy complete."