-
-
Notifications
You must be signed in to change notification settings - Fork 2
/
commands
executable file
·403 lines (321 loc) · 9.58 KB
/
commands
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
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
#!/usr/bin/env bash
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
source "$(dirname $0)/../common/functions"
require_clean_work_tree () {
trap "echo \"Uh-oh, something went wrong. Please ensure that /home/dokku is owned by dokku\"" INT EXIT TERM
dokku_log_info1_quiet "Checking dokku working directory..."
pushd "$DOKKU_ROOT" > /dev/null
local err=false
# Update the index
git update-index -q --ignore-submodules --refresh
# Disallow unstaged changes in the working tree
if ! git diff-files --quiet --ignore-submodules --; then
echo >&2 "cannot $1: $DOKKU_ROOT has unstaged changes."
git diff-files --name-status -r --ignore-submodules -- >&2
echo >&2 "Please stage and commit them."
err=true
fi
if [[ $(git show-ref) ]]; then
# Disallow uncommitted changes in the index
if ! git diff-index --cached --quiet HEAD --ignore-submodules --; then
echo >&2 "cannot $1: the index for $DOKKU_ROOT contains uncommitted changes."
git diff-index --cached --name-status -r --ignore-submodules HEAD -- >&2
echo >&2 "Please commit them."
err=true
fi
fi
# Disallow untracked files
local untracked
untracked=$(git ls-files --other --exclude-standard --exclude-from=.gitignore --exclude-per-directory=.gitignore --directory)
if [[ $untracked ]]; then
echo >&2 "cannot $1: $DOKKU_ROOT contains untracked files."
printf '%s\n' "${untracked[@]}" >&2
echo >&2 "Please stage and commit them, or add them to $DOKKU_ROOT/.gitignore"
err=true
fi
popd > /dev/null
trap - INT EXIT TERM
if $err; then
exit 1
fi
}
environment_url () {
grep -oP "(?<=\b$1=).+$" "$DOKKU_ROOT/GRADUATE_ENVIRONMENTS" 2> /dev/null
}
confirm () {
read -r -p "$1 [y/N]} " response
case $response in
[yY][eE][sS]|[yY])
true
;;
*)
exit 0
;;
esac
}
locate_apps () {
APPS=()
for app in $(dokku_apps); do
local running_containers=( $(find "$DOKKU_ROOT/$app/" -maxdepth 1 -name "CONTAINER*") )
if [ ${#running_containers[@]} -gt 0 ]; then
APPS+=( $app )
fi
done
}
prehooks () {
dokku_log_info1 "Executing pre-graduation hooks..."
if [[ -f "$DOKKU_ROOT/GRADUATE_PRE_HOOKS" ]]; then
bash -e "$DOKKU_ROOT/GRADUATE_PRE_HOOKS" && true
local hooks=$?
if (( hooks != 0 )); then
dokku_log_fail "Pre-graduation hooks failed. Graduation to $ENVIRONMENT cancelled."
fi
fi
}
abort_previous_app () {
ABORTING=true
let 'APP_INDEX -= 1' || true
if (( APP_INDEX >= 0 )); then
local app=${APPS[$APP_INDEX]}
local cmd="graduate:abort $app"
ssh -t dokku@$ENVIRONMENT_URL $cmd 2> /dev/null
fi
}
continue_previous_app () {
let 'APP_INDEX -= 1' || true
if (( APP_INDEX >= 0 )); then
local app=${APPS[$APP_INDEX]}
local cmd="graduate:continue $app"
ssh -t dokku@$ENVIRONMENT_URL $cmd 2> /dev/null
else
dokku_log_info1 "All apps deployed to $ENVIRONMENT."
fi
}
posthooks () {
dokku_log_info1 "Executing post-graduation hooks..."
if [[ -f "$DOKKU_ROOT/GRADUATE_POST_HOOKS" ]]; then
bash -e "$DOKKU_ROOT/GRADUATE_POST_HOOKS" && true
local hooks=$?
if (( hooks == 0 )); then
continue_previous_app
else
dokku_log_warn "Post-graduation hooks failed. Aborting graduation..."
abort_previous_app
fi
else
dokku_log_info1 "No hooks installed. Continuing..."
continue_previous_app
fi
}
deploy_next_app () {
let 'APP_INDEX += 1' || true
if (( APP_INDEX < ${#APPS[@]} )); then
local app=${APPS[$APP_INDEX]}
local appdir="$DOKKU_ROOT/$app"
dokku_log_info2 "Deploying $app..."
pushd $appdir > /dev/null
local deployed=true
set +e
git remote rm graduate &> /dev/null
git remote add graduate dokku@$ENVIRONMENT_URL:$app > /dev/null
if [[ $(git push graduate master -n 2>&1) == "Everything up-to-date" ]]; then
echo "Everything up-to-date"
deploy_next_app || deployed=false
else
( stdbuf -o0 git push graduate master 2>&1 | tee /dev/tty | grep --line-buffered "Waiting on graduating peers..." | ( read && deploy_next_app ) ) || deployed=false
fi
git remote rm graduate &> /dev/null
set -e
popd > /dev/null
if $deployed && ! $ABORTING; then
dokku_log_info2 "$app deployed"
continue_previous_app
else
dokku_log_warn "$app deployment aborted"
abort_previous_app
fi
else
posthooks
fi
}
deploy_apps () {
ABORTING=false
let "APP_INDEX = -1"
deploy_next_app
}
graduate () {
prehooks
dokku_log_info1 "Contacting $ENVIRONMENT..."
ssh -t dokku@$ENVIRONMENT_URL "graduate:clean" 2> /dev/null
sleep 5 # Give orphaned waiting containers on ENVIRONMENT time to read GRADUATE_STATUS
ssh -t dokku@$ENVIRONMENT_URL "graduate:start" 2> /dev/null
deploy_apps
if $ABORTING; then
exit 1
fi
ssh -t dokku@$ENVIRONMENT_URL "graduate:finish" 2> /dev/null
}
CMD="$1"
[[ $CMD ]] && {
shift
}
case "$CMD" in
graduate)
ENVIRONMENT=$1
[[ $ENVIRONMENT ]] && {
shift
}
export ENVIRONMENT_URL=$(environment_url $ENVIRONMENT)
if ! [[ $ENVIRONMENT_URL ]]; then
dokku_log_fail "Unknown environment: $ENVIRONMENT"
fi
locate_apps
if (( ${#APPS[@]} == 0 )); then
dokku_log_fail "There are no apps to graduate"
fi
require_clean_work_tree "graduate"
pushd "$DOKKU_ROOT" > /dev/null
export LAST_TAG=$(git tag | awk 'match($0, /'$ENVIRONMENT'\/[0-9]+/, a){print a[0]}' | tail -1)
if [[ $LAST_TAG ]]; then
CHANGES=$(git diff --exit-code --quiet $LAST_TAG..HEAD)$? || true
if (( CHANGES == 0 )); then
dokku_log_info1 "No changes to be deployed to $ENVIRONMENT"
exit 0
fi
git log --name-status $LAST_TAG..HEAD
else
git log --name-status
fi
confirm "Do you wish to proceed?"
graduate
git tag "$ENVIRONMENT/$(date +%Y%m%d%H%M%S)"
popd > /dev/null
;;
graduate:add-environment)
ENVIRONMENT=$1
[[ $ENVIRONMENT ]] && {
shift
URL=$1
[[ $URL ]] && {
shift
}
}
if [[ $ENVIRONMENT =~ ^[a-zA-Z0-9_./-]+$ ]]; then
if [[ $(environment_url $ENVIRONMENT) ]]; then
dokku_log_fail "$ENVIRONMENT already exists"
fi
echo "$ENVIRONMENT=$URL" >> "$DOKKU_ROOT/GRADUATE_ENVIRONMENTS"
dokku_log_info1_quiet "$ENVIRONMENT environment added"
else
dokku_log_fail "$ENVIRONMENT is not a valid environment name"
fi
;;
graduate:rm-environment)
ENVIRONMENT=$1
[[ $ENVIRONMENT ]] && {
shift
}
if ! [[ $(environment_url $ENVIRONMENT) ]]; then
dokku_log_fail "$ENVIRONMENT does not exist"
fi
awk '!/^'$ENVIRONMENT'=.+$/{print}' "$DOKKU_ROOT/GRADUATE_ENVIRONMENTS" > "$DOKKU_ROOT/TMP_GRADUATE_ENVIRONMENTS"
mv "$DOKKU_ROOT/TMP_GRADUATE_ENVIRONMENTS" "$DOKKU_ROOT/GRADUATE_ENVIRONMENTS"
;;
graduate:environments)
cat "$DOKKU_ROOT/GRADUATE_ENVIRONMENTS"
;;
graduate:add-hook)
HOOK_TYPE=$1
[[ $HOOK_TYPE ]] && {
shift
COMMAND=$*
[[ $COMMAND ]] && {
shift
}
}
if [[ "$HOOK_TYPE" != "PRE" && "$HOOK_TYPE" != "POST" ]]; then
dokku_log_fail "$HOOK_TYPE is not a valid hook type"
fi
ONE_LINE_COMMAND=$(echo "$COMMAND" | gawk 'BEGIN{ORS=" ";} {print gensub(/([^;[:space:]])\s*$/, "\\1;", "g");}')
echo "$ONE_LINE_COMMAND" >> "$DOKKU_ROOT/GRADUATE_${HOOK_TYPE}_HOOKS"
;;
graduate:rm-hook)
HOOK_TYPE=$1
[[ $HOOK_TYPE ]] && {
shift
LINE_NUMBER=$1
[[ $LINE_NUMBER ]] && {
shift
}
}
if [[ "$HOOK_TYPE" != "PRE" && "$HOOK_TYPE" != "POST" ]]; then
dokku_log_fail "$HOOK_TYPE is not a valid hook type"
fi
if [[ -f "$DOKKU_ROOT/GRADUATE_${HOOK_TYPE}_HOOKS" ]]; then
awk "!(NR==$LINE_NUMBER)" < "$DOKKU_ROOT/GRADUATE_${HOOK_TYPE}_HOOKS" > "$DOKKU_ROOT/TMP_GRADUATE_${HOOK_TYPE}_HOOKS"
mv "$DOKKU_ROOT/TMP_GRADUATE_${HOOK_TYPE}_HOOKS" "$DOKKU_ROOT/GRADUATE_${HOOK_TYPE}_HOOKS"
fi
;;
graduate:hooks)
HOOK_TYPE=$1
[[ $HOOK_TYPE ]] && {
shift
}
if [[ "$HOOK_TYPE" != "PRE" && "$HOOK_TYPE" != "POST" ]]; then
dokku_log_fail "$HOOK_TYPE is not a valid hook type"
fi
if [[ -f "$DOKKU_ROOT/GRADUATE_${HOOK_TYPE}_HOOKS" ]]; then
awk '{ print FNR " " $0 }' "$DOKKU_ROOT/GRADUATE_${HOOK_TYPE}_HOOKS"
fi
;;
graduate:key)
cat "/home/dokku/.ssh/id_rsa.pub"
;;
help | graduate:help)
HELP=$(cat<<EOF
graduate <environment>, Push all apps to the environment
graduate:add-environment <name> <url>, Add an environment.
graduate:rm-environment <name>, Remove an environment
graduate:environments, List environments
graduate:add-hook <PRE | POST> <command>, Add pre or post graduation hook
graduate:rm-hook <PRE | POST> <index>, Remove a hook by its index
graduate:hooks, List hooks with their indexes
graduate:key, Print this environments public key
EOF
)
if [[ -n $DOKKU_API_VERSION ]]; then
echo "$HELP"
else
cat && echo "$HELP"
fi
;;
# Private commands
graduate:clean)
echo "cleaning" > "$DOKKU_ROOT/GRADUATE_STATUS"
;;
graduate:start)
echo "deploying" > "$DOKKU_ROOT/GRADUATE_STATUS"
echo "Graduating apps..."
;;
graduate:finish)
echo "finished" > "$DOKKU_ROOT/GRADUATE_STATUS"
echo "Graduation complete."
;;
graduate:continue)
APP=$1
[[ $APP ]] && {
shift
}
echo "$APP:continue" > "$DOKKU_ROOT/GRADUATE_STATUS"
;;
graduate:abort)
APP=$1
[[ $APP ]] && {
shift
}
echo "$APP:abort" > "$DOKKU_ROOT/GRADUATE_STATUS"
;;
*)
exit $DOKKU_NOT_IMPLEMENTED_EXIT
;;
esac