-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Templatize and refactor 5.7+ entrypoint scripts #471
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f16150e
5a727ad
afceb7f
34f3ef2
a8aa1cf
ea73775
9e51c81
a9e8576
03bdbad
2242976
eb2821b
4672559
db12713
f9c185f
db2319e
33ba3e5
9f77ea5
ef9caa9
2fcb086
1503220
67f2bd3
5e10737
ae7b623
34ae313
c9600d2
f1abc95
915c792
9b90b1c
dfa4cb4
880bb34
04b03e0
71962c5
ce4d14d
964f6c2
125ac54
bbf5d01
06acf82
169471f
8a58acb
8d01eea
91785a5
206541a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,360 @@ | ||
#!/bin/bash | ||
set -eo pipefail | ||
shopt -s nullglob | ||
|
||
# logging functions | ||
mysql_log() { | ||
local type="$1"; shift | ||
printf '%s [%s] [Entrypoint]: %s\n' "$(date --rfc-3339=seconds)" "$type" "$*" | ||
} | ||
mysql_note() { | ||
mysql_log Note "$@" | ||
} | ||
mysql_warn() { | ||
mysql_log Warn "$@" >&2 | ||
} | ||
mysql_error() { | ||
mysql_log ERROR "$@" >&2 | ||
exit 1 | ||
} | ||
|
||
# usage: file_env VAR [DEFAULT] | ||
# ie: file_env 'XYZ_DB_PASSWORD' 'example' | ||
# (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of | ||
# "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature) | ||
file_env() { | ||
local var="$1" | ||
local fileVar="${var}_FILE" | ||
local def="${2:-}" | ||
if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then | ||
mysql_error "Both $var and $fileVar are set (but are exclusive)" | ||
fi | ||
local val="$def" | ||
if [ "${!var:-}" ]; then | ||
val="${!var}" | ||
elif [ "${!fileVar:-}" ]; then | ||
val="$(< "${!fileVar}")" | ||
fi | ||
export "$var"="$val" | ||
unset "$fileVar" | ||
} | ||
|
||
# check to see if this file is being run or sourced from another script | ||
_is_sourced() { | ||
# https://unix.stackexchange.com/a/215279 | ||
[ "${FUNCNAME[${#FUNCNAME[@]} - 1]}" == 'source' ] | ||
} | ||
|
||
# usage: docker_process_init_files [file [file [...]]] | ||
# ie: docker_process_init_files /always-initdb.d/* | ||
# process initializer files, based on file extensions | ||
docker_process_init_files() { | ||
# mysql here for backwards compatibility "${mysql[@]}" | ||
mysql=( docker_process_sql ) | ||
|
||
echo | ||
local f | ||
for f; do | ||
case "$f" in | ||
*.sh) mysql_note "$0: running $f"; . "$f" ;; | ||
*.sql) mysql_note "$0: running $f"; docker_process_sql < "$f"; echo ;; | ||
*.sql.gz) mysql_note "$0: running $f"; gunzip -c "$f" | docker_process_sql; echo ;; | ||
*) mysql_warn "$0: ignoring $f" ;; | ||
esac | ||
echo | ||
done | ||
} | ||
|
||
mysql_check_config() { | ||
local toRun=( "$@" --verbose --help ) errors | ||
if ! errors="$("${toRun[@]}" 2>&1 >/dev/null)"; then | ||
mysql_error $'mysqld failed while attempting to check config\n\tcommand was: '"${toRun[*]}"$'\n\t'"$errors" | ||
fi | ||
} | ||
|
||
# Fetch value from server config | ||
# We use mysqld --verbose --help instead of my_print_defaults because the | ||
# latter only show values present in config files, and not server defaults | ||
mysql_get_config() { | ||
local conf="$1"; shift | ||
"$@" --verbose --help --log-bin-index="$(mktemp -u)" 2>/dev/null \ | ||
| awk -v conf="$conf" '$1 == conf && /^[^ \t]/ { sub(/^[^ \t]+[ \t]+/, ""); print; exit }' | ||
# match "datadir /some/path with/spaces in/it here" but not "--xyz=abc\n datadir (xyz)" | ||
} | ||
|
||
# Do a temporary startup of the MySQL server, for init purposes | ||
docker_temp_server_start() { | ||
if [ "${MYSQL_MAJOR}" = '5.6' ]; then | ||
"$@" --skip-networking --socket="${SOCKET}" & | ||
mysql_note "Waiting for server startup" | ||
local i | ||
for i in {30..0}; do | ||
# only use the root password if the database has already been initializaed | ||
# so that it won't try to fill in a password file when it hasn't been set yet | ||
extraArgs=() | ||
if [ -z "$DATABASE_ALREADY_EXISTS" ]; then | ||
extraArgs+=( '--dont-use-mysql-root-password' ) | ||
fi | ||
if docker_process_sql "${extraArgs[@]}" --database=mysql <<<'SELECT 1' &> /dev/null; then | ||
break | ||
fi | ||
sleep 1 | ||
done | ||
if [ "$i" = 0 ]; then | ||
mysql_error "Unable to start server." | ||
fi | ||
else | ||
# For 5.7+ the server is ready for use as soon as startup command unblocks | ||
if ! "$@" --daemonize --skip-networking --socket="${SOCKET}"; then | ||
mysql_error "Unable to start server." | ||
fi | ||
fi | ||
} | ||
|
||
# Stop the server. When using a local socket file mysqladmin will block until | ||
# the shutdown is complete. | ||
docker_temp_server_stop() { | ||
if ! mysqladmin --defaults-extra-file=<( _mysql_passfile ) shutdown -uroot --socket="${SOCKET}"; then | ||
mysql_error "Unable to shut down server." | ||
fi | ||
} | ||
|
||
# Verify that the minimally required password settings are set for new databases. | ||
docker_verify_minimum_env() { | ||
if [ -z "$MYSQL_ROOT_PASSWORD" -a -z "$MYSQL_ALLOW_EMPTY_PASSWORD" -a -z "$MYSQL_RANDOM_ROOT_PASSWORD" ]; then | ||
yosifkit marked this conversation as resolved.
Show resolved
Hide resolved
|
||
mysql_error $'Database is uninitialized and password option is not specified\n\tYou need to specify one of MYSQL_ROOT_PASSWORD, MYSQL_ALLOW_EMPTY_PASSWORD and MYSQL_RANDOM_ROOT_PASSWORD' | ||
fi | ||
} | ||
|
||
# creates folders for the database | ||
# also ensures permission for user mysql of run as root | ||
docker_create_db_directories() { | ||
local user; user="$(id -u)" | ||
|
||
# TODO other directories that are used by default? like /var/lib/mysql-files | ||
# see https://github.com/docker-library/mysql/issues/562 | ||
mkdir -p "$DATADIR" | ||
|
||
if [ "$user" = "0" ]; then | ||
# this will cause less disk access than `chown -R` | ||
find "$DATADIR" \! -user mysql -exec chown mysql '{}' + | ||
fi | ||
} | ||
|
||
# initializes the database directory | ||
docker_init_database_dir() { | ||
mysql_note "Initializing database files" | ||
if [ "$MYSQL_MAJOR" = '5.6' ]; then | ||
mysql_install_db --datadir="$DATADIR" --rpm --keep-my-cnf "${@:2}" | ||
else | ||
"$@" --initialize-insecure | ||
fi | ||
mysql_note "Database files initialized" | ||
|
||
if command -v mysql_ssl_rsa_setup > /dev/null && [ ! -e "$DATADIR/server-key.pem" ]; then | ||
# https://github.com/mysql/mysql-server/blob/23032807537d8dd8ee4ec1c4d40f0633cd4e12f9/packaging/deb-in/extra/mysql-systemd-start#L81-L84 | ||
mysql_note "Initializing certificates" | ||
mysql_ssl_rsa_setup --datadir="$DATADIR" | ||
mysql_note "Certificates initialized" | ||
fi | ||
} | ||
|
||
# Loads various settings that are used elsewhere in the script | ||
# This should be called after mysql_check_config, but before any other functions | ||
docker_setup_env() { | ||
# Get config | ||
declare -g DATADIR SOCKET | ||
DATADIR="$(mysql_get_config 'datadir' "$@")" | ||
SOCKET="$(mysql_get_config 'socket' "$@")" | ||
yosifkit marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
# Initialize values that might be stored in a file | ||
file_env 'MYSQL_ROOT_HOST' '%' | ||
file_env 'MYSQL_DATABASE' | ||
file_env 'MYSQL_USER' | ||
file_env 'MYSQL_PASSWORD' | ||
file_env 'MYSQL_ROOT_PASSWORD' | ||
|
||
declare -g DATABASE_ALREADY_EXISTS | ||
if [ -d "$DATADIR/mysql" ]; then | ||
DATABASE_ALREADY_EXISTS='true' | ||
fi | ||
} | ||
|
||
# Execute sql script, passed via stdin | ||
# usage: docker_process_sql [--dont-use-mysql-root-password] [mysql-cli-args] | ||
# ie: docker_process_sql --database=mydb <<<'INSERT ...' | ||
# ie: docker_process_sql --dont-use-mysql-root-password --database=mydb <my-file.sql | ||
docker_process_sql() { | ||
passfileArgs=() | ||
if [ '--dont-use-mysql-root-password' = "$1" ]; then | ||
passfileArgs+=( "$1" ) | ||
shift | ||
fi | ||
# args sent in can override this db, since they will be later in the command | ||
if [ -n "$MYSQL_DATABASE" ]; then | ||
set -- --database="$MYSQL_DATABASE" "$@" | ||
fi | ||
|
||
mysql --defaults-file=<( _mysql_passfile "${passfileArgs[@]}") --protocol=socket -uroot -hlocalhost --socket="${SOCKET}" "$@" | ||
} | ||
|
||
# Initializes database with timezone info and root password, plus optional extra db/user | ||
docker_setup_db() { | ||
# Load timezone info into database | ||
if [ -z "$MYSQL_INITDB_SKIP_TZINFO" ]; then | ||
# sed is for https://bugs.mysql.com/bug.php?id=20545 | ||
mysql_tzinfo_to_sql /usr/share/zoneinfo \ | ||
| sed 's/Local time zone must be set--see zic manual page/FCTY/' \ | ||
| docker_process_sql --dont-use-mysql-root-password --database=mysql | ||
# tell docker_process_sql to not use MYSQL_ROOT_PASSWORD since it is not set yet | ||
fi | ||
# Generate random root password | ||
if [ -n "$MYSQL_RANDOM_ROOT_PASSWORD" ]; then | ||
export MYSQL_ROOT_PASSWORD="$(pwgen -1 32)" | ||
yosifkit marked this conversation as resolved.
Show resolved
Hide resolved
|
||
mysql_note "GENERATED ROOT PASSWORD: $MYSQL_ROOT_PASSWORD" | ||
yosifkit marked this conversation as resolved.
Show resolved
Hide resolved
|
||
fi | ||
# Sets root password and creates root users for non-localhost hosts | ||
local rootCreate= | ||
# default root to listen for connections from anywhere | ||
if [ -n "$MYSQL_ROOT_HOST" ] && [ "$MYSQL_ROOT_HOST" != 'localhost' ]; then | ||
# no, we don't care if read finds a terminating character in this heredoc | ||
# https://unix.stackexchange.com/questions/265149/why-is-set-o-errexit-breaking-this-read-heredoc-expression/265151#265151 | ||
read -r -d '' rootCreate <<-EOSQL || true | ||
CREATE USER 'root'@'${MYSQL_ROOT_HOST}' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}' ; | ||
GRANT ALL ON *.* TO 'root'@'${MYSQL_ROOT_HOST}' WITH GRANT OPTION ; | ||
EOSQL | ||
fi | ||
|
||
local passwordSet= | ||
if [ "$MYSQL_MAJOR" = '5.6' ]; then | ||
# no, we don't care if read finds a terminating character in this heredoc (see above) | ||
read -r -d '' passwordSet <<-EOSQL || true | ||
DELETE FROM mysql.user WHERE user NOT IN ('mysql.sys', 'mysqlxsys', 'root') OR host NOT IN ('localhost') ; | ||
SET PASSWORD FOR 'root'@'localhost'=PASSWORD('${MYSQL_ROOT_PASSWORD}') ; | ||
EOSQL | ||
else | ||
# no, we don't care if read finds a terminating character in this heredoc (see above) | ||
read -r -d '' passwordSet <<-EOSQL || true | ||
ALTER USER 'root'@'localhost' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}' ; | ||
EOSQL | ||
fi | ||
|
||
# tell docker_process_sql to not use MYSQL_ROOT_PASSWORD since it is just now being set | ||
docker_process_sql --dont-use-mysql-root-password --database=mysql <<-EOSQL | ||
-- What's done in this file shouldn't be replicated | ||
-- or products like mysql-fabric won't work | ||
SET @@SESSION.SQL_LOG_BIN=0; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this still relevant? (It's not used in later invocations of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think this is the only outstanding question and since it is about code that isn't new, I think we can put it in its own issue. |
||
|
||
${passwordSet} | ||
GRANT ALL ON *.* TO 'root'@'localhost' WITH GRANT OPTION ; | ||
FLUSH PRIVILEGES ; | ||
${rootCreate} | ||
DROP DATABASE IF EXISTS test ; | ||
EOSQL | ||
|
||
# Creates a custom database and user if specified | ||
if [ -n "$MYSQL_DATABASE" ]; then | ||
mysql_note "Creating database ${MYSQL_DATABASE}" | ||
docker_process_sql --database=mysql <<<"CREATE DATABASE IF NOT EXISTS \`$MYSQL_DATABASE\` ;" | ||
fi | ||
|
||
if [ -n "$MYSQL_USER" ] && [ -n "$MYSQL_PASSWORD" ]; then | ||
mysql_note "Creating user ${MYSQL_USER}" | ||
docker_process_sql --database=mysql <<<"CREATE USER '$MYSQL_USER'@'%' IDENTIFIED BY '$MYSQL_PASSWORD' ;" | ||
|
||
if [ -n "$MYSQL_DATABASE" ]; then | ||
mysql_note "Giving user ${MYSQL_USER} access to schema ${MYSQL_DATABASE}" | ||
docker_process_sql --database=mysql <<<"GRANT ALL ON \`$MYSQL_DATABASE\`.* TO '$MYSQL_USER'@'%' ;" | ||
fi | ||
|
||
docker_process_sql --database=mysql <<<"FLUSH PRIVILEGES ;" | ||
fi | ||
} | ||
|
||
_mysql_passfile() { | ||
yosifkit marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# echo the password to the "file" the client uses | ||
# the client command will use process substitution to create a file on the fly | ||
# ie: --defaults-file=<( _mysql_passfile ) | ||
if [ '--dont-use-mysql-root-password' != "$1" ] && [ -n "$MYSQL_ROOT_PASSWORD" ]; then | ||
cat <<-EOF | ||
[client] | ||
password="${MYSQL_ROOT_PASSWORD}" | ||
EOF | ||
fi | ||
} | ||
|
||
# Mark root user as expired so the password must be changed before anything | ||
# else can be done (only supported for 5.6+) | ||
mysql_expire_root_user() { | ||
if [ -n "$MYSQL_ONETIME_PASSWORD" ]; then | ||
docker_process_sql --database=mysql <<-EOSQL | ||
ALTER USER 'root'@'%' PASSWORD EXPIRE; | ||
EOSQL | ||
fi | ||
} | ||
|
||
# check arguments for an option that would cause mysqld to stop | ||
# return true if there is one | ||
_mysql_want_help() { | ||
local arg | ||
for arg; do | ||
case "$arg" in | ||
-'?'|--help|--print-defaults|-V|--version) | ||
return 0 | ||
;; | ||
esac | ||
done | ||
return 1 | ||
} | ||
|
||
_main() { | ||
# if command starts with an option, prepend mysqld | ||
if [ "${1:0:1}" = '-' ]; then | ||
set -- mysqld "$@" | ||
fi | ||
|
||
# skip setup if they aren't running mysqld or want an option that stops mysqld | ||
if [ "$1" = 'mysqld' ] && ! _mysql_want_help "$@"; then | ||
mysql_note "Entrypoint script for MySQL Server ${MYSQL_VERSION} started." | ||
|
||
mysql_check_config "$@" | ||
# Load various environment variables | ||
docker_setup_env "$@" | ||
docker_create_db_directories | ||
|
||
# If container is started as root user, restart as dedicated mysql user | ||
if [ "$(id -u)" = "0" ]; then | ||
mysql_note "Switching to dedicated user 'mysql'" | ||
exec gosu mysql "$BASH_SOURCE" "$@" | ||
fi | ||
|
||
# there's no database, so it needs to be initialized | ||
if [ -z "$DATABASE_ALREADY_EXISTS" ]; then | ||
docker_verify_minimum_env | ||
docker_init_database_dir "$@" | ||
|
||
mysql_note "Starting temporary server" | ||
docker_temp_server_start "$@" | ||
mysql_note "Temporary server started." | ||
|
||
docker_setup_db | ||
docker_process_init_files /docker-entrypoint-initdb.d/* | ||
|
||
mysql_expire_root_user | ||
|
||
mysql_note "Stopping temporary server" | ||
docker_temp_server_stop | ||
mysql_note "Temporary server stopped" | ||
|
||
echo | ||
mysql_note "MySQL init process done. Ready for start up." | ||
echo | ||
fi | ||
fi | ||
exec "$@" | ||
} | ||
|
||
# If we are sourced from elsewhere, don't perform any further actions | ||
if ! _is_sourced; then | ||
_main "$@" | ||
fi |
Uh oh!
There was an error while loading. Please reload this page.