Skip to content

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

Merged
merged 42 commits into from
Oct 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
f16150e
Unify entrypoint scripts for 5.7 and 8.0
ltangvald May 28, 2018
5a727ad
Make template for 5.7+ entrypoint script
ltangvald May 28, 2018
afceb7f
Template: Create functions for starting and stopping server during init
ltangvald Aug 6, 2018
34f3ef2
Template: Rename template directory to be hidden
ltangvald Aug 22, 2018
a8aa1cf
Template: Create logging functions
ltangvald Aug 22, 2018
ea73775
Template: Store root password in file
ltangvald Sep 6, 2018
9e51c81
Template: Use mysqladmin to stop temporary server
ltangvald Sep 6, 2018
a9e8576
Template: Use --daemonize for temporary server startup.
ltangvald Sep 6, 2018
03bdbad
Add 5.5 and 5.6 to entrypoint templating
ltangvald Sep 13, 2018
2242976
Move flag for password file to when the client command is first defined
ltangvald Sep 13, 2018
eb2821b
Fix typo templaing->templating
ltangvald Sep 20, 2018
4672559
Prefix function names in entrypoint with "docker"
ltangvald Sep 21, 2018
db12713
entrypoint: Use mktemp instead of install
ltangvald Oct 18, 2018
f9c185f
entrypoint: Move more logic into functions
ltangvald Oct 18, 2018
db2319e
entrypoint: Move main script functionality to function
ltangvald Oct 22, 2018
33ba3e5
entrypoint: Only execute main function if the script is not sourced
ltangvald Nov 5, 2018
9f77ea5
entrypoint: Make value checks more consistent
ltangvald Nov 8, 2018
ef9caa9
Rename docker_check_config to mysql_check_config
ltangvald Apr 1, 2019
2fcb086
Rename docker_file_env to file_env
ltangvald Apr 1, 2019
1503220
Rename docker_verify_env to docker_verify_minimum_env
ltangvald Apr 1, 2019
67f2bd3
Rename docker_process_init_file to docker_process_init_files
ltangvald Apr 1, 2019
5e10737
Rename docker_write_password_file to mysql_write_password_file
ltangvald Apr 1, 2019
ae7b623
Rename docker_get_config to mysql_get_config
ltangvald Apr 4, 2019
34ae313
Rename docker_init_env to docker_setup_env
ltangvald Apr 4, 2019
c9600d2
Rename log functions from docker to mysql
ltangvald Apr 4, 2019
f1abc95
Rename docker_main to _main
ltangvald Apr 4, 2019
915c792
Rename functions for starting and stopping server
ltangvald Apr 4, 2019
9b90b1c
Move old-mysql-only logic for waiting for server startup into function
ltangvald Apr 4, 2019
dfa4cb4
Rename docker_expire_root_user to mysql_expire_root_user
ltangvald Apr 4, 2019
880bb34
Rename docker_init_database_user to docker_setup_db_users
ltangvald May 8, 2019
04b03e0
Merge functions mysql_write_password_file, docker_init_root_user and
ltangvald May 8, 2019
71962c5
Rename function docker_init_database_dir to docker_create_db_directories
ltangvald May 8, 2019
ce4d14d
Merge function docker_generate_root_password into docker_setup_db
ltangvald May 8, 2019
964f6c2
Merge function docker_load_tzinfo into docker_setup_db
ltangvald May 8, 2019
125ac54
Replace function docker_init_client_command with docker_process_sql
ltangvald May 8, 2019
bbf5d01
Remove empty function docker_wait_for_server
ltangvald May 8, 2019
06acf82
A few entrypoint updates to increase usability
yosifkit May 16, 2019
169471f
Apply update.sh to update each entrypoint; drop 5.5 from update.sh
yosifkit May 24, 2019
8a58acb
Adjustments from tianon's comments
yosifkit Jun 3, 2019
8d01eea
Fix source detection for centos, call check_config first, explicit gl…
yosifkit Aug 17, 2019
91785a5
Add --dont-use-mysql-root-password flag for docker_process_sql
yosifkit Sep 18, 2019
206541a
Adjust printf to be more resilient; use exit code directly instead of…
yosifkit Sep 18, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
360 changes: 360 additions & 0 deletions .template.Debian/docker-entrypoint.sh
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
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' "$@")"

# 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)"
mysql_note "GENERATED ROOT PASSWORD: $MYSQL_ROOT_PASSWORD"
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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this still relevant? (It's not used in later invocations of docker_process_sql.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this still relevant? (It's not used in later invocations of docker_process_sql.)

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() {
# 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
Loading