-
Notifications
You must be signed in to change notification settings - Fork 1
/
server
executable file
·275 lines (246 loc) · 10.6 KB
/
server
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
#!/bin/bash
source ./lib/singlenv
source ./lib/colorful
source ./lib/env
source ./lib/utils
source "lib/plugin"
# server [city] action [module] [actionargs]
# Run added modules (this will be automatically normalized to 'up --detach' before passing to docker-compose)
# ---------------------
# server run
# Stop added modules
# ---------------------
# server stop
# View running containers
# ---------------------
# server ls
# Restart added modules
# ---------------------
# server restart
# Shut down added modules
# ---------------------
# server down
# Pause added modules
# ---------------------
# server pause
# Unpause added modules
# ---------------------
# server unpause
# other commands that docker-compose accepts
# Initalize and validate
projectname=`basename "$PWD"`
args=( $@ )
declare -A citiesPerModule
## if the first argument is a valid city configuration file and provided in the form 'Country-City' or 'Country-City.env'
## e.g.: server [city] action [module] [actionargs]
## ....
cityScope="all"
envfile="${args[0]/'.env'/''}.env"
if [ -f "./config/$envfile" ]; then # export the variables in the city configuration file
export $(cat ./config/$envfile | xargs)
cityScope="city"
blueecho "performing some checks & normalizations ..."
source ./lib/validation # will re-init variable 'args' and will normalize it with self-provided function 'removeFirstElemInArgs'
curCity="$city" # only to provide analog behaviour to the comming 'curAction', 'curModule' and 'curActionArgs' variables but not needed for this architecture to work properly except for plugins using 'curCity'
fi
if [ ${#args[@]} -eq 0 ]; then
redecho "Error: Not enough arguments!"
echo "USAGE: ./server [city] action [module] [actionargs]"
echo ""
echo "[city] Optional, name of city e.g. Bolivia-Cochabamba"
echo "action Required, name of the action to execute/perform e.g. 'start'"
echo "[module] Optional, name of the module to execute/perform action against e.g. 'tileserver'"
echo "[actionargs] Optional, additional arguments for the action to execute/perform e.g. '--build --detach' for action 'up'"
exit 1
fi
## e.g.: server [city] action [module] [actionargs]
## ......
curAction="${args[0]}"
action="$curAction"
removeFirstElemInArgs
## e.g.: server [city] action [module] [actionargs]
## ........
#if [ -n "${args[0]}" ] && [ -d "./modules/${args[0]}" ]; then
curModule="${args[0]}"
removeFirstElemInArgs
#fi
## e.g.: server [city] action [module] [actionargs]
## ............
curActionArgs="${args[@]}"
# Define the core
## batch execution (based on what an iterator has found previously)
performExecution() {
if [ -z "${!citiesPerModule[@]}" ]; then # a message for developers
redecho "Trying to call 'performExecution' without running 'performIteration \"noexecute\"' before leads to this error message!"
echo "crying ..."
return
fi
for module in "${!citiesPerModule[@]}"; do
orangeecho "performing desired action '$curAction' on module '$module' ..."
if [ -d "$module" ]; then
local oldpwd="$PWD"
cd "$module"
local cities=( ${citiesPerModule[$module]} )
for city in "${cities[@]}"; do
orangeecho " - in city $city ..."
if [ -n "$1" ]; then # assume a function when given
"$@" "$module" "$city";
else
sudo docker-compose -p "${projectname}" -f "$city.yml" $curAction $curActionArgs
fi
done
cd "$oldpwd"
fi
done
}
## iterators for modules
### perform on all cities of a module
iterateThroughAllCitiesOfModule() {
cd "modules/$1"
addedCities=""
for composeFile in `ls | grep .yml`; do
if ! [ "$composeFile" = "docker-compose.yml" ] && [[ "$composeFile" == *.yml ]] && [ -f "$composeFile" ]; then
addedCities+="${composeFile/'.yml'/''} "
fi
done
if [ -n "$addedCities" ]; then
citiesPerModule["modules/$1"]=$addedCities
fi
cd "../../"
}
### perform on all modules in a city
iterateThroughAllModulesInCity() {
cd modules
for module in `ls -p ./ | grep /`; do
module=${module/\//""}
cd "$module"
if [ -f "$1.yml" ]; then
citiesPerModule["modules/$module"]="$1"
fi
cd ../
done
cd ../
}
### perform on all modules in all cities
iterateThroughEverything() {
for module in `ls -p modules | grep /`; do
local module=${module/\//""}
iterateThroughAllCitiesOfModule $module $1
done
}
### determine iteration strategy to use and call it
performIteration() {
local error=""
if [ "$cityScope" = "all" ] && [ -z "$curModule" ]; then # no city and no module have been specified (perform on all modules in all cities)
# server action
iterateThroughEverything $1 # perform on all modules in all cities
elif [ "$cityScope" = "all" ] && [ -n "$curModule" ]; then # no city but a module have been specified
# server action module [actionargs]
iterateThroughAllCitiesOfModule $curModule $1 # perform on all cities of a module
elif [ "$cityScope" = "city" ] && [ -z "$curModule" ]; then # a city but no module have been specified
# server city action
iterateThroughAllModulesInCity $city $1 # perform on all modules in current city
elif [ "$cityScope" = "city" ] && [ -n "$curModule" ]; then # a city and a module have been specified
# server city action module [actionargs]
citiesPerModule["modules/$curModule"]="$city"
else # if this else gets called then it means something is wrong with the code
redecho "Error: Action '$curAction' couldn't be run as an iteration strategy to perform couldn't be found!"
local error="ITERATIONSTRATEGYNOTFOUND"
fi
if [ "$1" = "execute" ] && [ -z "$error" ]; then
local locargs=( $@ )
unset locargs[0]
locargs=( `echo "${locargs[@]}"` )
performExecution ${locargs[@]}
fi
}
## attention please prompt
attentionPrompt() {
local reason="$1"
local cityrelatedtext=""
if [ -z "$reason" ]; then
reason="(no reason provided)"
fi
redecho "----------------------------------------"
echo -e "The action '$curAction' asks for\033[1;m your approval\033[0;m before continuing! There must be a\033[0;31m dangerous situation\033[0;m it wants you to be aware of"
echo -e "Reason: \033[0;34m${reason}\033[0;m"
echo -e "\033[1;37mExecution details:\033[0;m"
echo -e "Scope: \033[0;34m${cityScope}\033[0;m"
if [ "$cityScope" = "city" ]; then
echo -e "Affected city: \033[0;34m${city}\033[0;m"
local cityrelatedtext=" in city '$city'"
else
echo -e "Affected city: \033[0;34m(action will be applied to all cities when approving)\033[0;m"
fi
if [ -n "$curModule" ]; then
echo -e "Affected module: \033[0;34m${curModule}\033[0;m"
else
echo -e "Affected module: \033[0;34m(action will be applied to all modules${cityrelatedtext} when approving)\033[0;m"
fi
echo -e "Action: \033[0;34m${curAction}\033[0;m"
echo -e "Action arguments: \033[0;34m${curActionArgs}\033[0;m"
echo -e "Current date and time: \033[0;34m$(date)\033[0;m"
redecho "----------------------------------------"
decide() {
echo -ne "\033[0;35mYour decision [y|n]:\033[0;m \033[0;34m"
read decision
if [ "$decision" = "n" ]; then
greenecho "Action '$curAction' has been interrupted at your request. Nothing dangerous has been executed!"
echo "Script interrupted by user"
exit 2
elif [ "$decision" = "y" ]; then
orangeecho "Execution of action '$curAction' continues!"
return
else
redecho "Invalid input. Only 'y' and 'n' (case-sensitive) counts!"
decide
fi
}
decide
}
# Call actions (made by plugins)
pluginargs=( ${moreargs[@]} )
invokeSinglePlugin "server" "$curAction"
if [ $pluginsInvoked -eq 1 ]; then # a plugin hasn't been invoked
if [ "$curAction" = "down" ] || [ "$curAction" = "stop" ]; then
attentionPrompt "Are you sure that nothing depends on the modules you want to perform action '$curAction' on?"
performIteration "execute"
else # no additional behaviour defined so pass to docker-compose directly
performIteration "execute"
fi
fi
# Detect and add plugins for chiefs (files in 'plugins/chief' ending in '.yml')
chief_plugins=""
for i in `ls plugins/chief | grep "\.yml$"`; do
chief_plugins+="-f plugins/chief/$i"
done
# Determine if there are still running services after certain action has been performed
## Difference between 'grep "${projectname}-chief"' and '-p "${projectname}"':
## grep "${projectname}-chief": This is a combination of the project name being the same for all modules and all chiefs running under this structure AND the prefix of the service names in the docker-compose.yml
## -p "${projectname}": This is the project name of the structure itself. It is being the same for all modules and all chiefs running under this structure
runningServices=`sudo docker container ls | grep "${projectname}" | wc -l` # count running services including the chief ones
allServiceContainers=`sudo docker container ls --all | grep "${projectname}" | wc -l` # count all services including the chief ones
runningChiefs=`sudo docker container ls | grep "${projectname}_chief-" | wc -l` # count running chiefs
allChiefContainers=`sudo docker container ls --all | grep "${projectname}_chief-" | wc -l`
allChiefsAvailable=`sudo docker-compose -p "${projectname}" -f docker-compose.yml $chief_plugins ps --services | wc -l` # count all chiefs
## if no service is running (except the chiefs)
if [ 0 -eq $((runningServices-runningChiefs)) ]; then
### check if they have been removed completely (except the chiefs)
if [ 0 -eq $((allServiceContainers-runningChiefs)) ]; then # also stop and remove all chiefs
orangeecho "turning down chiefs as there are no docker containers left ..."
sudo docker-compose -p "${projectname}" -f "docker-compose.yml" $chief_plugins down # so we can even turn down and remove the chief ones
else ### not all services have been removed but they have been stopped (chiefs not counted in)
orangeecho "stopping chiefs as there are only stopped containers ..."
sudo docker-compose -p "${projectname}" -f "docker-compose.yml" $chief_plugins stop # so we just stop the chiefs
fi
else ## if services are running (chiefs not counted in)
### if amount of chiefs having containers is greater than the amount of running ones
if [ $allChiefContainers -gt $runningChiefs ]; then
orangeecho "start chiefs as there are running docker containers ..."
sudo docker-compose -p "${projectname}" -f "docker-compose.yml" $chief_plugins start # start them all
### if amount of chiefs (including ones without a container) is greater than the amount of existing chief containers
elif [ $allChiefsAvailable -gt $allChiefContainers ]; then
orangeecho "turning up chiefs as there are running docker containers ..."
sudo docker-compose -p "${projectname}" -f "docker-compose.yml" $chief_plugins up --build --detach # wire up the missing chiefs
fi
fi