Skip to content

Commit 06acf82

Browse files
committed
A few entrypoint updates to increase usability
`docker-entrypoint.sh`: - update `docker_process_init_files` to take in a list of file - update `docker_process_sql` to take sql input on stdin to maintain compatibility with `mysql` (and prevent us from loading an entire `.sql` file into memory) - move any code that was outside a function to `_main` or other functions - use `local` variables in functions to limit scope - move current `docker_create_db_directories` logic to `docker_init_database_dir` - new docker_create_db_directories that just creates and chowns directories - move `PASSFILE` to be in memory only so that we don't accidentally not clean it up - move `FLUSH PRIVILEGES` to fix #549 update.sh: - add back `DELETE` statement for 5.5 and 5.6 - swap some template strings to single quotes to make it easier to read - swap `sed` to use ! for delimter - use one loop rather than two
1 parent bbf5d01 commit 06acf82

File tree

2 files changed

+117
-108
lines changed

2 files changed

+117
-108
lines changed

.template.Debian/docker-entrypoint.sh

+102-90
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ shopt -s nullglob
44

55
# logging functions
66
mysql_log() {
7-
local type=$1;shift
7+
local type="$1"; shift
88
printf "$(date --rfc-3339=seconds) [${type}] [Entrypoint]: $@\n"
99
}
1010
mysql_note() {
@@ -18,22 +18,6 @@ mysql_error() {
1818
exit 1
1919
}
2020

21-
# if command starts with an option, prepend mysqld
22-
if [ "${1:0:1}" = '-' ]; then
23-
set -- mysqld "$@"
24-
fi
25-
26-
# skip setup if they want an option that stops mysqld
27-
wantHelp=
28-
for arg; do
29-
case "$arg" in
30-
-'?'|--help|--print-defaults|-V|--version)
31-
wantHelp=1
32-
break
33-
;;
34-
esac
35-
done
36-
3721
# usage: file_env VAR [DEFAULT]
3822
# ie: file_env 'XYZ_DB_PASSWORD' 'example'
3923
# (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of
@@ -55,25 +39,28 @@ file_env() {
5539
unset "$fileVar"
5640
}
5741

58-
# usage: docker_process_init_files FILENAME MYSQLCOMMAND...
59-
# ie: docker_process_init_files foo.sh mysql -uroot
60-
# (process a single initializer file, based on its extension. we define this
61-
# function here, so that initializer scripts (*.sh) can use the same logic,
62-
# potentially recursively, or override the logic used in subsequent calls)
42+
# usage: docker_process_init_files [file [file [...]]]
43+
# ie: docker_process_init_files /always-initdb.d/*
44+
# process initializer files, based on file extensions
6345
docker_process_init_files() {
64-
local f="$1"; shift
65-
66-
case "$f" in
67-
*.sh) mysql_note "$0: running $f"; . "$f" ;;
68-
*.sql) mysql_note "$0: running $f"; docker_process_sql "$(cat $f)"; echo ;;
69-
*.sql.gz) mysql_note "$0: running $f"; docker_process_sql "$(gunzip -c $f)"; echo ;;
70-
*) mysql_warn "$0: ignoring $f" ;;
71-
esac
46+
# mysql here for backwards compatibility "${mysql[@]}"
47+
mysql=( docker_process_sql )
48+
7249
echo
50+
local f
51+
for f; do
52+
case "$f" in
53+
*.sh) mysql_note "$0: running $f"; . "$f" ;;
54+
*.sql) mysql_note "$0: running $f"; docker_process_sql < "$f"; echo ;;
55+
*.sql.gz) mysql_note "$0: running $f"; gunzip -c "$f" | docker_process_sql; echo ;;
56+
*) mysql_warn "$0: ignoring $f" ;;
57+
esac
58+
echo
59+
done
7360
}
7461

7562
mysql_check_config() {
76-
toRun=( "$@" --verbose --help )
63+
local toRun=( "$@" --verbose --help ) errors
7764
if ! errors="$("${toRun[@]}" 2>&1 >/dev/null)"; then
7865
mysql_error "mysqld failed while attempting to check config\n\tcommand was: ${toRun[*]}\n\t$errors"
7966
fi
@@ -85,13 +72,13 @@ mysql_check_config() {
8572
mysql_get_config() {
8673
local conf="$1"; shift
8774
"$@" --verbose --help --log-bin-index="$(mktemp -u)" 2>/dev/null \
88-
| awk '$1 == "'"$conf"'" && /^[^ \t]/ { sub(/^[^ \t]+[ \t]+/, ""); print; exit }'
75+
| awk -v conf="$conf" '$1 == conf && /^[^ \t]/ { sub(/^[^ \t]+[ \t]+/, ""); print; exit }'
8976
# match "datadir /some/path with/spaces in/it here" but not "--xyz=abc\n datadir (xyz)"
9077
}
9178

9279
# Do a temporary startup of the MySQL server, for init purposes
9380
docker_temp_server_start() {
94-
result=0
81+
local result=0
9582
%%SERVERSTARTUP%%
9683
if [ "$result" != "0" ];then
9784
mysql_error "Unable to start server. Status code $result."
@@ -100,8 +87,9 @@ docker_temp_server_start() {
10087
# For 5.7+ the server is ready for use as soon as startup command unblocks
10188
if [ "${MYSQL_MAJOR}" = "5.5" ] || [ "${MYSQL_MAJOR}" = "5.6" ]; then
10289
mysql_note "Waiting for server startup"
90+
local i
10391
for i in {30..0}; do
104-
if docker_process_sql "SELECT 1" &> /dev/null; then
92+
if docker_process_sql --database=mysql <<<'SELECT 1' &> /dev/null; then
10593
break
10694
fi
10795
sleep 1
@@ -115,8 +103,8 @@ docker_temp_server_start() {
115103
# Stop the server. When using a local socket file mysqladmin will block until
116104
# the shutdown is complete.
117105
docker_temp_server_stop() {
118-
result=0
119-
mysqladmin --defaults-extra-file="${PASSFILE}" shutdown -uroot --socket="${SOCKET}" || result=$?
106+
local result=0
107+
mysqladmin --defaults-extra-file=<( echo "${PASSFILE}" ) shutdown -uroot --socket="${SOCKET}" || result=$?
120108
if [ "$result" != "0" ]; then
121109
mysql_error "Unable to shut down server. Status code $result."
122110
fi
@@ -129,10 +117,23 @@ docker_verify_minimum_env() {
129117
fi
130118
}
131119

132-
# Creates and initializes the database directory
120+
# creates folders for the database
121+
# also ensures permission for user mysql of run as root
133122
docker_create_db_directories() {
123+
local user; user="$(id -u)"
124+
125+
# TODO other directories that are used by default? like /var/lib/mysql-files
126+
# see https://github.com/docker-library/mysql/issues/562
134127
mkdir -p "$DATADIR"
135128

129+
if [ "$user" = "0" ]; then
130+
# this will cause less disk access than `chown -R`
131+
find "$DATADIR" \! -user mysql -exec chown mysql '{}' +
132+
fi
133+
}
134+
135+
# initializes the database directory
136+
docker_init_database_dir() {
136137
mysql_note "Initializing database files"
137138
%%DATABASEINIT%%
138139
mysql_note "Database files initialized"
@@ -150,11 +151,7 @@ docker_setup_env() {
150151
# Get config
151152
DATADIR="$(mysql_get_config 'datadir' "$@")"
152153
SOCKET="$(mysql_get_config 'socket' "$@")"
153-
154-
# We create a file to store the root password in so we don''t use it on the command line
155-
TMPDIR="$(mktemp -d)"
156-
PASSFILE="$(mktemp ${TMPDIR}/XXXXXXXXXX)"
157-
154+
158155
# Initialize values that might be stored in a file
159156
file_env 'MYSQL_ROOT_HOST' '%'
160157
file_env 'MYSQL_DATABASE'
@@ -163,27 +160,25 @@ docker_setup_env() {
163160
file_env 'MYSQL_ROOT_PASSWORD'
164161
}
165162

166-
# Execute sql script
163+
# Execute sql script, passed via stdin
164+
# usage: docker_process_sql [mysql-cli-args]
165+
# ie: docker_process_sql --database=mydb <<<'INSERT ...'
166+
# ie: docker_process_sql --database=mydb <my-file.sql
167167
docker_process_sql() {
168-
SQL=$1
169-
DB=$2
170-
if [ -z "$SQL" ]; then
171-
mysql_error "Empty sql script provided"
168+
# args sent in can override this db, since they will be later in the command
169+
if [ -n "$MYSQL_DATABASE" ]; then
170+
set -- --database="$MYSQL_DATABASE" "$@"
172171
fi
173-
echo "$SQL" | mysql --defaults-file="${PASSFILE}" --protocol=socket -uroot -hlocalhost --socket="${SOCKET}" "$DB"
174-
}
175172

176-
# Define the client command that's used in various places
177-
docker_init_client_command() {
178-
mysql=( mysql --defaults-file="${PASSFILE}" --protocol=socket -uroot -hlocalhost --socket="${SOCKET}" )
173+
mysql --defaults-file=<( echo "${PASSFILE}" ) --protocol=socket -uroot -hlocalhost --socket="${SOCKET}" "$@"
179174
}
180175

181176
# Initializes database with timezone info and root password, plus optional extra db/user
182177
docker_setup_db() {
183178
# Load timezone info into database
184179
if [ -z "$MYSQL_INITDB_SKIP_TZINFO" ]; then
185180
# sed is for https://bugs.mysql.com/bug.php?id=20545
186-
docker_process_sql "$(mysql_tzinfo_to_sql /usr/share/zoneinfo | sed 's/Local time zone must be set--see zic manual page/FCTY/')" mysql
181+
mysql_tzinfo_to_sql /usr/share/zoneinfo | sed 's/Local time zone must be set--see zic manual page/FCTY/' | docker_process_sql --database=mysql
187182
fi
188183
# Generate random root password
189184
if [ ! -z "$MYSQL_RANDOM_ROOT_PASSWORD" ]; then
@@ -202,102 +197,117 @@ docker_setup_db() {
202197
EOSQL
203198
fi
204199

205-
docker_process_sql "
200+
docker_process_sql --database=mysql <<-EOSQL
206201
-- What's done in this file shouldn't be replicated
207202
-- or products like mysql-fabric won't work
208203
SET @@SESSION.SQL_LOG_BIN=0;
209204
210205
%%PASSWORDSET%%
211206
GRANT ALL ON *.* TO 'root'@'localhost' WITH GRANT OPTION ;
207+
FLUSH PRIVILEGES ;
212208
${rootCreate}
213209
DROP DATABASE IF EXISTS test ;
214-
FLUSH PRIVILEGES ;
215-
"
210+
EOSQL
216211

217-
# Write the password to the file the client uses
212+
# Write the password to the "file" the client uses
213+
# the client command will use process substitution to create a file on the fly
214+
# ie: --defaults-file=<( echo "${PASSFILE}" )
218215
if [ ! -z "$MYSQL_ROOT_PASSWORD" ]; then
219-
cat >"${PASSFILE}" <<EOF
220-
[client]
221-
password="${MYSQL_ROOT_PASSWORD}"
222-
EOF
216+
read -r -d '' PASSFILE <<-EOF || true
217+
[client]
218+
password="${MYSQL_ROOT_PASSWORD}"
219+
EOF
223220
fi
224221

225222
# Creates a custom database and user if specified
226223
if [ "$MYSQL_DATABASE" ]; then
227224
mysql_note "Creating database ${MYSQL_DATABASE}"
228-
docker_process_sql "CREATE DATABASE IF NOT EXISTS \`$MYSQL_DATABASE\` ;"
229-
mysql+=( "$MYSQL_DATABASE" )
225+
docker_process_sql --database=mysql <<<"CREATE DATABASE IF NOT EXISTS \`$MYSQL_DATABASE\` ;"
230226
fi
231227

232228
if [ "$MYSQL_USER" -a "$MYSQL_PASSWORD" ]; then
233229
mysql_note "Creating user ${MYSQL_USER}"
234-
docker_process_sql "CREATE USER '$MYSQL_USER'@'%' IDENTIFIED BY '$MYSQL_PASSWORD' ;"
230+
docker_process_sql --database=mysql <<<"CREATE USER '$MYSQL_USER'@'%' IDENTIFIED BY '$MYSQL_PASSWORD' ;"
235231

236232
if [ "$MYSQL_DATABASE" ]; then
237233
mysql_note "Giving user ${MYSQL_USER} access to schema ${MYSQL_DATABASE}"
238-
docker_process_sql "GRANT ALL ON \`$MYSQL_DATABASE\`.* TO '$MYSQL_USER'@'%' ;"
234+
docker_process_sql --database=mysql <<<"GRANT ALL ON \`$MYSQL_DATABASE\`.* TO '$MYSQL_USER'@'%' ;"
239235
fi
240236

241-
docker_process_sql "FLUSH PRIVILEGES ;"
237+
docker_process_sql --database=mysql <<<"FLUSH PRIVILEGES ;"
242238
fi
243239
}
244240

245241
# Mark root user as expired so the password must be changed before anything
246242
# else can be done (only supported for 5.6+)
247243
mysql_expire_root_user() {
248-
if [ "${MYSQL_MAJOR}" = "5.5" ]; then
249-
mysql_warn "MySQL 5.5 does not support PASSWORD EXPIRE (required for MYSQL_ONETIME_PASSWORD)"
250-
else
251-
docker_process_sql "ALTER USER 'root'@'%' PASSWORD EXPIRE;"
244+
if [ ! -z "$MYSQL_ONETIME_PASSWORD" ]; then
245+
if [ "${MYSQL_MAJOR}" = "5.5" ]; then
246+
mysql_warn "MySQL 5.5 does not support PASSWORD EXPIRE (required for MYSQL_ONETIME_PASSWORD)"
247+
else
248+
docker_process_sql --database=mysql <<-EOSQL
249+
ALTER USER 'root'@'%' PASSWORD EXPIRE;
250+
EOSQL
251+
fi
252252
fi
253253
}
254254

255+
# check arguments for an option that would cause mysqld to stop
256+
# return true if there is one
257+
_want_help() {
258+
local arg
259+
for arg; do
260+
case "$arg" in
261+
-'?'|--help|--print-defaults|-V|--version)
262+
return 0
263+
;;
264+
esac
265+
done
266+
return 1
267+
}
268+
255269
_main() {
256-
mysql_note "Entrypoint script for MySQL Server ${MYSQL_VERSION} started."
270+
# if command starts with an option, prepend mysqld
271+
if [ "${1:0:1}" = '-' ]; then
272+
set -- mysqld "$@"
273+
fi
274+
275+
# skip setup if they aren't running mysqld or want an option that stops mysqld
276+
if [ "$1" = 'mysqld' ] && ! _want_help "$@"; then
277+
mysql_note "Entrypoint script for MySQL Server ${MYSQL_VERSION} started."
257278

258-
if [ "$1" = 'mysqld' -a -z "$wantHelp" ]; then
259279
# Load various environment variables
260280
docker_setup_env "$@"
281+
mysql_check_config "$@"
261282

262283
# If container is started as root user, restart as dedicated mysql user
263-
if [ "$(id -u)" = '0' ]; then
264-
mysql_check_config "$@"
265-
mkdir -p "$DATADIR"
266-
chown -R mysql:mysql "$DATADIR"
284+
if [ "$(id -u)" = "0" ]; then
285+
docker_create_db_directories
267286
mysql_note "Switching to dedicated user 'mysql'"
268287
exec gosu mysql "$BASH_SOURCE" "$@"
269288
fi
270289

271-
# still need to check config, container may have started with --user
272-
mysql_check_config "$@"
290+
# just in case the script was not started as root
291+
docker_create_db_directories
273292

274293
# If this is true then there's no database, and it needs to be initialized
275294
if [ ! -d "$DATADIR/mysql" ]; then
276295
docker_verify_minimum_env
277-
docker_create_db_directories "$@"
278-
docker_init_client_command
296+
docker_init_database_dir "$@"
279297

280298
mysql_note "Starting temporary server"
281299
docker_temp_server_start "$@"
282300
mysql_note "Temporary server started."
283301

284-
285302
docker_setup_db
303+
docker_process_init_files /docker-entrypoint-initdb.d/*
286304

287-
echo
288-
for f in /docker-entrypoint-initdb.d/*; do
289-
docker_process_init_files "$f"
290-
done
305+
mysql_expire_root_user
291306

292-
if [ ! -z "$MYSQL_ONETIME_PASSWORD" ]; then
293-
mysql_expire_root_user
294-
fi
295307
mysql_note "Stopping temporary server"
296308
docker_temp_server_stop
297309
mysql_note "Temporary server stopped"
298310

299-
# Remove the password file now that initialization is complete
300-
rm -f "${PASSFILE}"
301311
unset PASSFILE
302312
echo
303313
mysql_note "MySQL init process done. Ready for start up."
@@ -306,8 +316,10 @@ _main() {
306316
fi
307317
exec "$@"
308318
}
319+
309320
# This checks if the script has been sourced from elsewhere.
310321
# If so we don't perform any further actions
322+
# https://unix.stackexchange.com/a/215279
311323
if [ "${FUNCNAME[${#FUNCNAME[@]} - 1]}" != 'source' ]; then
312324
_main "$@"
313325
fi

0 commit comments

Comments
 (0)