-
Notifications
You must be signed in to change notification settings - Fork 0
/
pianka.sh
executable file
·374 lines (304 loc) · 10.6 KB
/
pianka.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
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
#!/usr/bin/env bash
_SHORT_OPTIONS="
h C: L: v
"
_LONG_OPTIONS="
help composer-name: composer-location: verbose
"
APP_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
set -euo pipefail
APP_CACHE_DIR="${APP_DIR}/.pianka-cache-dir"
mkdir -pv "${APP_CACHE_DIR}" &> /dev/null
CMDNAME="$(basename -- "$0")"
KUBECONFIG=$(mktemp)
export KUBECONFIG
function add_trap() {
trap="${1}"
shift
for signal in "${@}"
do
# adding trap to exiting trap
local handlers
handlers="$( trap -p "${signal}" | cut -f2 -d \' )"
# shellcheck disable=SC2064
trap "${trap};${handlers}" "${signal}"
done
}
# shellcheck disable=SC2016
add_trap 'rm -f "${KUBECONFIG}"' EXIT HUP INT TERM
function save_to_file {
# shellcheck disable=SC2005
echo "$(eval echo "\$$1")" > "${APP_CACHE_DIR}/.$1"
}
function read_from_file {
cat "${APP_CACHE_DIR}/.$1" 2>/dev/null || true
}
# Composer global variables
COMPOSER_NAME=$(read_from_file COMPOSER_NAME)
export COMPOSER_NAME=${COMPOSER_NAME:=}
COMPOSER_LOCATION=$(read_from_file COMPOSER_LOCATION)
export COMPOSER_LOCATION=${COMPOSER_LOCATION:=}
export VERBOSE="false"
usage() {
cat << EOF
Usage: ${CMDNAME} [-h] [-C] [-L] [-v] <command>
Help manage Cloud Composer instances
The script is adapted to work properly when added to the PATH variable. This will allow you to use
this script from any location.
Flags:
-h, --help
Shows this help message.
-C, --composer-name <COMPOSER_NAME>
Composer instance used to run the operations on. Defaults to ${COMPOSER_NAME}
-L, --composer-location <COMPOSER_LOCATION>
Composer locations. Defaults to ${COMPOSER_LOCATION}
-v, --verbose
Add even more verbosity when running the script.
These are supported commands used in various situations:
shell
Open shell access to Airflow's worker. This allows you to test commands in the context of
the Airflow instance.
info
Print basic information about the environment.
run
Run arbitrary command on the Airflow worker.
Example:
If you want to list currnet running process, run:
${CMDNAME} run -- ps -aux
If you want to list DAGs, run:
${CMDNAME} run -- airflow list_dags
mysql
Starts the MySQL console.
Additional parameters are passed to the mysql client.
Example:
If you want to execute "SELECT 123" query, run:
${CMDNAME} mysql -- --execute="SELECT 123"
mysqltunnel
Starts the tunnel to MySQL database.
This allows you to connect to the database with any tool, including your IDE.
mysqldump
Dumps database or selected table(s).
Additional parameters are passed to the mysqldump.
To dump "connection" table to "connection.sql" file, run:
${CMDNAME} mysqldump -- --column-statistics=0 connection > connection.sql
Reference:
https://dev.mysql.com/doc/refman/8.0/en/mysqldump.html
help
Print help
EOF
echo
}
set +e
getopt -T >/dev/null
GETOPT_RETVAL=$?
if [[ ${GETOPT_RETVAL} != 4 ]]; then
echo
if [[ $(uname -s) == 'Darwin' ]] ; then
echo "You are running ${CMDNAME} in OSX environment"
echo "And you need to install gnu commands"
echo
echo "Run 'brew install gnu-getopt coreutils'"
echo
echo "Then link the gnu-getopt to become default as suggested by brew by typing:"
echo "echo 'export PATH=\"/usr/local/opt/gnu-getopt/bin:\$PATH\"' >> ~/.bash_profile"
echo ". ~/.bash_profile"
echo
echo "Login and logout afterwards"
echo
else
echo "You do not have necessary tools in your path (getopt). Please install the"
echo "Please install latest/GNU version of getopt."
echo "This can usually be done with 'apt install util-linux'"
fi
echo
exit 1
fi
if ! PARAMS=$(getopt \
-o "${_SHORT_OPTIONS:=}" \
-l "${_LONG_OPTIONS:=}" \
--name "$CMDNAME" -- "$@")
then
usage
exit 1
fi
eval set -- "${PARAMS}"
unset PARAMS
# Parse Flags.
while true
do
case "${1}" in
-h|--help)
usage;
exit 0 ;;
-C|--composer-name)
export COMPOSER_NAME="${2}";
shift 2 ;;
-L|--composer-location)
export COMPOSER_LOCATION="${2}";
shift 2 ;;
-v|--verbose)
export VERBOSE="true";
echo "Verbosity turned on" >&2
shift ;;
--)
shift ;
break ;;
*)
usage
echo "ERROR: Unknown argument ${1}"
exit 1
;;
esac
done
if [ -z "$COMPOSER_NAME" ] && [ -z "$COMPOSER_LOCATION" ] ; then
echo 'The configuration of the environment is unknown.'
echo 'Execute this program with "--composer-name" and "--composer-location" flags to set the current environment.'
echo "The values will be saved and subsequent starts will not require configuration."
exit 1
fi
save_to_file COMPOSER_NAME
save_to_file COMPOSER_LOCATION
# Utils
function log() {
if [[ ${VERBOSE} == "true" ]]; then
echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2
fi
}
# Run functions
function run_command_on_composer {
log "Running \"$*\" command on \"${COMPOSER_GKE_WORKER_NAME}\""
kubectl exec --namespace="${COMPOSER_GKE_NAMESPACE_NAME}" -t "${COMPOSER_GKE_WORKER_NAME}" --container airflow-worker -- "$@"
}
function run_interactive_command_on_composer {
log "Running \"$*\" command on \"${COMPOSER_GKE_WORKER_NAME}\""
kubectl exec --namespace="${COMPOSER_GKE_NAMESPACE_NAME}" -it "${COMPOSER_GKE_WORKER_NAME}" --container airflow-worker -- "$@"
}
# Fetch info functions
function fetch_composer_gke_info {
log "Fetching information about the GKE cluster"
COMPOSER_GKE_CLUSTER_NAME=$(gcloud beta composer environments describe "${COMPOSER_NAME}" --location "${COMPOSER_LOCATION}" '--format=value(config.gkeCluster)')
gcloud container clusters get-credentials "${COMPOSER_GKE_CLUSTER_NAME}" --zone "any" &>/dev/null
COMPOSER_GKE_NAMESPACE_NAME=$(kubectl get namespaces | grep "composer" | cut -d " " -f 1)
COMPOSER_GKE_WORKER_NAME=$(kubectl get pods --namespace="${COMPOSER_GKE_NAMESPACE_NAME}" | grep "airflow-worker" | grep "Running" | head -1 | cut -d " " -f 1)
if [[ ${COMPOSER_GKE_WORKER_NAME} == "" ]]; then
echo "No running airflow-worker!"
exit 1
fi
log "GKE Cluster Name: ${COMPOSER_GKE_CLUSTER_NAME}"
log "GKE Worker Name: ${COMPOSER_GKE_WORKER_NAME}"
}
function fetch_composer_bucket_info {
log "Fetching information about the bucket"
COMPOSER_DAG_BUCKET=$(gcloud beta composer environments describe "${COMPOSER_NAME}" --location "${COMPOSER_LOCATION}" --format='value(config.dagGcsPrefix)')
COMPOSER_DAG_BUCKET=${COMPOSER_DAG_BUCKET%/dags}
COMPOSER_DAG_BUCKET=${COMPOSER_DAG_BUCKET#gs://}
log "DAG Bucket: ${COMPOSER_DAG_BUCKET}"
}
function fetch_composer_webui_info {
log "Fetching information about the GCS bucket"
COMPOSER_WEB_UI_URL=$(gcloud beta composer environments describe "${COMPOSER_NAME}" --location "${COMPOSER_LOCATION}" --format='value(config.airflowUri)')
log "WEB UI URL: ${COMPOSER_WEB_UI_URL}"
}
function fetch_composer_mysql_credentials {
log "Fetching MySQL credentials"
# shellcheck disable=SC2016
COMPOSER_MYSQL_URL="$(run_command_on_composer bash -c 'echo $AIRFLOW__CORE__SQL_ALCHEMY_CONN')"
[[ ${COMPOSER_MYSQL_URL} =~ ([^:]*)://([^@/]*)@?([^/:]*):?([0-9]*)/([^\?]*)\??(.*) ]] && \
DETECTED_MYSQL_AUTHINFO=${BASH_REMATCH[2]} &&
COMPOSER_MYSQL_HOST=${BASH_REMATCH[3]} &&
COMPOSER_MYSQL_DATABASE=${BASH_REMATCH[5]}
COMPOSER_MYSQL_USER="$(echo "${DETECTED_MYSQL_AUTHINFO}" | cut -d ":" -f 1)"
COMPOSER_MYSQL_PASSWORD="$(echo "${DETECTED_MYSQL_AUTHINFO}" | cut -d ":" -f 2)"
log "SQL Alchemy URL: ${COMPOSER_MYSQL_URL}"
log " Host: ${COMPOSER_MYSQL_HOST}"
log " User: ${COMPOSER_MYSQL_USER}"
log " Password: ${COMPOSER_MYSQL_PASSWORD}"
log " Database: ${COMPOSER_MYSQL_DATABASE}"
}
if [[ "$#" -eq 0 ]]; then
echo "You must provide at least one command."
usage
exit 1
fi
CMD=$1
shift
if [[ "${CMD}" == "shell" ]] ; then
fetch_composer_gke_info
run_interactive_command_on_composer /bin/bash
exit 0
elif [[ "${CMD}" == "info" ]] ; then
fetch_composer_bucket_info
echo "DAG Bucket: ${COMPOSER_DAG_BUCKET}"
fetch_composer_gke_info
echo "GKE Cluster Name: ${COMPOSER_GKE_CLUSTER_NAME}"
echo "GKE Worker Name: ${COMPOSER_GKE_WORKER_NAME}"
fetch_composer_webui_info
echo "WEB UI URL: ${COMPOSER_WEB_UI_URL}"
fetch_composer_mysql_credentials
echo "SQL Alchemy URL: ${COMPOSER_MYSQL_URL}"
echo " Host: ${COMPOSER_MYSQL_HOST}"
echo " User: ${COMPOSER_MYSQL_USER}"
echo " Password: ${COMPOSER_MYSQL_PASSWORD}"
echo " Database: ${COMPOSER_MYSQL_DATABASE}"
exit 0
elif [[ "${CMD}" == "run" ]] ; then
fetch_composer_gke_info
run_command_on_composer "$@"
exit 0
elif [[ "${CMD}" == "mysql" ]] ; then
fetch_composer_gke_info
fetch_composer_mysql_info
fetch_composer_gke_info
fetch_composer_mysql_info
run_interactive_command_on_composer \
mysql \
--user="${COMPOSER_MYSQL_USER}" \
--password="${COMPOSER_MYSQL_PASSWORD}" \
--host="${COMPOSER_MYSQL_HOST}" \
"${COMPOSER_MYSQL_DATABASE}" \
"$@"
exit 0
elif [[ "${CMD}" == "mysqltunnel" ]] ; then
fetch_composer_gke_info
fetch_composer_mysql_credentials
fetch_composer_gke_info
echo "To connect, run:"
echo "mysql \\"
echo " --user='${COMPOSER_MYSQL_USER}' \\"
echo " --password='${COMPOSER_MYSQL_PASSWORD}' \\"
echo " --host=127.0.0.1 \\"
echo " --port=3306 \\"
echo " '${COMPOSER_MYSQL_DATABASE}'"
echo ""
echo "or"
echo ""
echo "Configure IDE to use this connection URI:"
echo "jdbc:mysql://root:${COMPOSER_MYSQL_PASSWORD}@127.0.0.1:3306/${COMPOSER_MYSQL_DATABASE}"
kubectl port-forward \
--namespace="default" \
"deployment/airflow-sqlproxy" \
3306
exit 0
elif [[ "${CMD}" == "mysqldump" ]] ; then
fetch_composer_gke_info
fetch_composer_mysql_credentials
fetch_composer_gke_info
kubectl port-forward \
--namespace="default" \
"deployment/airflow-sqlproxy" \
3306 1>&2 &
sleep 5;
TUNNEL_PID=$!
# shellcheck disable=SC2064,SC2016
add_trap '$(kill '${TUNNEL_PID}' || true)' EXIT HUP INT TERM
mysqldump \
--user="${COMPOSER_MYSQL_USER}" \
--password="${COMPOSER_MYSQL_PASSWORD}" \
--host="127.0.0.1" \
--port=3306 \
"${COMPOSER_MYSQL_DATABASE}" "$@"
exit 0
else
usage
exit 0
fi