[COMMON] Add custom certs into AAF truststore
[oom.git] / kubernetes / portal / components / portal-mariadb / resources / config / mariadb / docker-entrypoint.sh
1 #!/bin/bash
2 set -eo pipefail
3 shopt -s nullglob
4
5 # logging functions
6 mysql_log() {
7         local type="$1"; shift
8         printf '%s [%s] [Entrypoint]: %s\n' "$(date --rfc-3339=seconds)" "$type" "$*"
9 }
10 mysql_note() {
11         mysql_log Note "$@"
12 }
13 mysql_warn() {
14         mysql_log Warn "$@" >&2
15 }
16 mysql_error() {
17         mysql_log ERROR "$@" >&2
18         exit 1
19 }
20
21 # usage: file_env VAR [DEFAULT]
22 #    ie: file_env 'XYZ_DB_PASSWORD' 'example'
23 # (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of
24 #  "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature)
25 file_env() {
26         local var="$1"
27         local fileVar="${var}_FILE"
28         local def="${2:-}"
29         if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then
30                 mysql_error "Both $var and $fileVar are set (but are exclusive)"
31         fi
32         local val="$def"
33         if [ "${!var:-}" ]; then
34                 val="${!var}"
35         elif [ "${!fileVar:-}" ]; then
36                 val="$(< "${!fileVar}")"
37         fi
38         export "$var"="$val"
39         unset "$fileVar"
40 }
41
42 # check to see if this file is being run or sourced from another script
43 _is_sourced() {
44         # https://unix.stackexchange.com/a/215279
45         [ "${#FUNCNAME[@]}" -ge 2 ] \
46                 && [ "${FUNCNAME[0]}" = '_is_sourced' ] \
47                 && [ "${FUNCNAME[1]}" = 'source' ]
48 }
49
50 # usage: docker_process_init_files [file [file [...]]]
51 #    ie: docker_process_init_files /always-initdb.d/*
52 # process initializer files, based on file extensions
53 docker_process_init_files() {
54         # mysql here for backwards compatibility "${mysql[@]}"
55         mysql=( docker_process_sql )
56
57         echo
58         local f
59         for f; do
60                 case "$f" in
61                         *.sh)
62                                 # https://github.com/docker-library/postgres/issues/450#issuecomment-393167936
63                                 # https://github.com/docker-library/postgres/pull/452
64                                 if [ -x "$f" ]; then
65                                         mysql_note "$0: running $f"
66                                         "$f"
67                                 else
68                                         mysql_note "$0: sourcing $f"
69                                         . "$f"
70                                 fi
71                                 ;;
72                         *.sql)    mysql_note "$0: running $f"; docker_process_sql < "$f"; echo ;;
73                         *.sql.gz) mysql_note "$0: running $f"; gunzip -c "$f" | docker_process_sql; echo ;;
74                         *.sql.xz) mysql_note "$0: running $f"; xzcat "$f" | docker_process_sql; echo ;;
75                         *)        mysql_warn "$0: ignoring $f" ;;
76                 esac
77                 echo
78         done
79 }
80
81 mysql_check_config() {
82         local toRun=( "$@" --verbose --help --log-bin-index="$(mktemp -u)" ) errors
83         if ! errors="$("${toRun[@]}" 2>&1 >/dev/null)"; then
84                 mysql_error $'mysqld failed while attempting to check config\n\tcommand was: '"${toRun[*]}"$'\n\t'"$errors"
85         fi
86 }
87
88 # Fetch value from server config
89 # We use mysqld --verbose --help instead of my_print_defaults because the
90 # latter only show values present in config files, and not server defaults
91 mysql_get_config() {
92         local conf="$1"; shift
93         "$@" --verbose --help --log-bin-index="$(mktemp -u)" 2>/dev/null \
94                 | awk -v conf="$conf" '$1 == conf && /^[^ \t]/ { sub(/^[^ \t]+[ \t]+/, ""); print; exit }'
95         # match "datadir      /some/path with/spaces in/it here" but not "--xyz=abc\n     datadir (xyz)"
96 }
97
98 # Do a temporary startup of the MySQL server, for init purposes
99 docker_temp_server_start() {
100         "$@" --skip-networking --socket="${SOCKET}" &
101         mysql_note "Waiting for server startup"
102         local i
103         for i in {30..0}; do
104                 # only use the root password if the database has already been initializaed
105                 # so that it won't try to fill in a password file when it hasn't been set yet
106                 extraArgs=()
107                 if [ -z "$DATABASE_ALREADY_EXISTS" ]; then
108                         extraArgs+=( '--dont-use-mysql-root-password' )
109                 fi
110                 if docker_process_sql "${extraArgs[@]}" --database=mysql <<<'SELECT 1' &> /dev/null; then
111                         break
112                 fi
113                 sleep 1
114         done
115         if [ "$i" = 0 ]; then
116                 mysql_error "Unable to start server."
117         fi
118 }
119
120 # Stop the server. When using a local socket file mysqladmin will block until
121 # the shutdown is complete.
122 docker_temp_server_stop() {
123         if ! mysqladmin --defaults-extra-file=<( _mysql_passfile ) shutdown -uroot --socket="${SOCKET}"; then
124                 mysql_error "Unable to shut down server."
125         fi
126 }
127
128 # Verify that the minimally required password settings are set for new databases.
129 docker_verify_minimum_env() {
130         if [ -z "$MYSQL_ROOT_PASSWORD" -a -z "$MYSQL_ALLOW_EMPTY_PASSWORD" -a -z "$MYSQL_RANDOM_ROOT_PASSWORD" ]; then
131                 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'
132         fi
133 }
134
135 # creates folders for the database
136 # also ensures permission for user mysql of run as root
137 docker_create_db_directories() {
138         local user; user="$(id -u)"
139
140         # TODO other directories that are used by default? like /var/lib/mysql-files
141         # see https://github.com/docker-library/mysql/issues/562
142         mkdir -p "$DATADIR"
143
144         if [ "$user" = "0" ]; then
145                 # this will cause less disk access than `chown -R`
146                 find "$DATADIR" \! -user mysql -exec chown mysql '{}' +
147         fi
148 }
149
150 # initializes the database directory
151 docker_init_database_dir() {
152         mysql_note "Initializing database files"
153         installArgs=( --datadir="$DATADIR" --rpm )
154         if { mysql_install_db --help || :; } | grep -q -- '--auth-root-authentication-method'; then
155                 # beginning in 10.4.3, install_db uses "socket" which only allows system user root to connect, switch back to "normal" to allow mysql root without a password
156                 # see https://github.com/MariaDB/server/commit/b9f3f06857ac6f9105dc65caae19782f09b47fb3
157                 # (this flag doesn't exist in 10.0 and below)
158                 installArgs+=( --auth-root-authentication-method=normal )
159         fi
160         # "Other options are passed to mysqld." (so we pass all "mysqld" arguments directly here)
161         mysql_install_db "${installArgs[@]}" "${@:2}"
162         mysql_note "Database files initialized"
163 }
164
165 # Loads various settings that are used elsewhere in the script
166 # This should be called after mysql_check_config, but before any other functions
167 docker_setup_env() {
168         # Get config
169         declare -g DATADIR SOCKET
170         DATADIR="$(mysql_get_config 'datadir' "$@")"
171         SOCKET="$(mysql_get_config 'socket' "$@")"
172
173         # Initialize values that might be stored in a file
174         file_env 'MYSQL_ROOT_HOST' '%'
175         file_env 'MYSQL_DATABASE'
176         file_env 'MYSQL_USER'
177         file_env 'MYSQL_PASSWORD'
178         file_env 'MYSQL_ROOT_PASSWORD'
179         file_env 'PORTAL_DB_TABLES'
180
181         declare -g DATABASE_ALREADY_EXISTS
182         if [ -d "$DATADIR/mysql" ]; then
183                 DATABASE_ALREADY_EXISTS='true'
184         fi
185 }
186
187 # Execute sql script, passed via stdin
188 # usage: docker_process_sql [--dont-use-mysql-root-password] [mysql-cli-args]
189 #    ie: docker_process_sql --database=mydb <<<'INSERT ...'
190 #    ie: docker_process_sql --dont-use-mysql-root-password --database=mydb <my-file.sql
191 docker_process_sql() {
192         passfileArgs=()
193         if [ '--dont-use-mysql-root-password' = "$1" ]; then
194                 passfileArgs+=( "$1" )
195                 shift
196         fi
197         # args sent in can override this db, since they will be later in the command
198         if [ -n "$MYSQL_DATABASE" ]; then
199                 set -- --database="$MYSQL_DATABASE" "$@"
200         fi
201
202         mysql --defaults-extra-file=<( _mysql_passfile "${passfileArgs[@]}") --protocol=socket -uroot -hlocalhost --socket="${SOCKET}" "$@"
203 }
204
205 # Initializes database with timezone info and root password, plus optional extra db/user
206 docker_setup_db() {
207         # Load timezone info into database
208         if [ -z "$MYSQL_INITDB_SKIP_TZINFO" ]; then
209                 {
210                         # Aria in 10.4+ is slow due to "transactional" (crash safety)
211                         # https://jira.mariadb.org/browse/MDEV-23326
212                         # https://github.com/docker-library/mariadb/issues/262
213                         local tztables=( time_zone time_zone_leap_second time_zone_name time_zone_transition time_zone_transition_type )
214                         for table in "${tztables[@]}"; do
215                                 echo "/*!100400 ALTER TABLE $table TRANSACTIONAL=0 */;"
216                         done
217
218                         # sed is for https://bugs.mysql.com/bug.php?id=20545
219                         mysql_tzinfo_to_sql /usr/share/zoneinfo \
220                                 | sed 's/Local time zone must be set--see zic manual page/FCTY/'
221
222                         for table in "${tztables[@]}"; do
223                                 echo "/*!100400 ALTER TABLE $table TRANSACTIONAL=1 */;"
224                         done
225                 } | docker_process_sql --dont-use-mysql-root-password --database=mysql
226                 # tell docker_process_sql to not use MYSQL_ROOT_PASSWORD since it is not set yet
227         fi
228         # Generate random root password
229         if [ -n "$MYSQL_RANDOM_ROOT_PASSWORD" ]; then
230                 export MYSQL_ROOT_PASSWORD="$(pwgen -1 32)"
231                 mysql_note "GENERATED ROOT PASSWORD: $MYSQL_ROOT_PASSWORD"
232         fi
233         # Sets root password and creates root users for non-localhost hosts
234         local rootCreate=
235         # default root to listen for connections from anywhere
236         if [ -n "$MYSQL_ROOT_HOST" ] && [ "$MYSQL_ROOT_HOST" != 'localhost' ]; then
237                 # no, we don't care if read finds a terminating character in this heredoc
238                 # https://unix.stackexchange.com/questions/265149/why-is-set-o-errexit-breaking-this-read-heredoc-expression/265151#265151
239                 read -r -d '' rootCreate <<-EOSQL || true
240                         CREATE USER 'root'@'${MYSQL_ROOT_HOST}' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}' ;
241                         GRANT ALL ON *.* TO 'root'@'${MYSQL_ROOT_HOST}' WITH GRANT OPTION ;
242                 EOSQL
243         fi
244
245         # tell docker_process_sql to not use MYSQL_ROOT_PASSWORD since it is just now being set
246         docker_process_sql --dont-use-mysql-root-password --database=mysql <<-EOSQL
247                 -- What's done in this file shouldn't be replicated
248                 --  or products like mysql-fabric won't work
249                 SET @@SESSION.SQL_LOG_BIN=0;
250
251                 DELETE FROM mysql.user WHERE user NOT IN ('mysql.sys', 'mariadb.sys', 'mysqlxsys', 'root') OR host NOT IN ('localhost') ;
252                 SET PASSWORD FOR 'root'@'localhost'=PASSWORD('${MYSQL_ROOT_PASSWORD}') ;
253                 -- 10.1: https://github.com/MariaDB/server/blob/d925aec1c10cebf6c34825a7de50afe4e630aff4/scripts/mysql_secure_installation.sh#L347-L365
254                 -- 10.5: https://github.com/MariaDB/server/blob/00c3a28820c67c37ebbca72691f4897b57f2eed5/scripts/mysql_secure_installation.sh#L351-L369
255                 DELETE FROM mysql.db WHERE Db='test' OR Db='test\_%' ;
256
257                 GRANT ALL ON *.* TO 'root'@'localhost' WITH GRANT OPTION ;
258                 FLUSH PRIVILEGES ;
259                 ${rootCreate}
260                 DROP DATABASE IF EXISTS test ;
261         EOSQL
262
263         # Creates a custom database and user if specified
264         if [ -n "$MYSQL_DATABASE" ]; then
265                 mysql_note "Creating database ${MYSQL_DATABASE}"
266                 docker_process_sql --database=mysql <<<"CREATE DATABASE IF NOT EXISTS \`$MYSQL_DATABASE\` ;"
267         fi
268
269         if [ -n "$MYSQL_USER" ] && [ -n "$MYSQL_PASSWORD" ]; then
270                 mysql_note "Creating user ${MYSQL_USER}"
271                 docker_process_sql --database=mysql <<<"CREATE USER '$MYSQL_USER'@'%' IDENTIFIED BY '$MYSQL_PASSWORD' ;"
272
273                 if [ -n "$MYSQL_DATABASE" ]; then
274                         mysql_note "Giving user ${MYSQL_USER} access to schema ${MYSQL_DATABASE}"
275                         docker_process_sql --database=mysql <<<"GRANT ALL ON \`${MYSQL_DATABASE//_/\\_}\`.* TO '$MYSQL_USER'@'%' ;"
276                 fi
277
278                 docker_process_sql --database=mysql <<<"FLUSH PRIVILEGES ;"
279         fi
280 }
281
282 _mysql_passfile() {
283         # echo the password to the "file" the client uses
284         # the client command will use process substitution to create a file on the fly
285         # ie: --defaults-extra-file=<( _mysql_passfile )
286         if [ '--dont-use-mysql-root-password' != "$1" ] && [ -n "$MYSQL_ROOT_PASSWORD" ]; then
287                 cat <<-EOF
288                         [client]
289                         password="${MYSQL_ROOT_PASSWORD}"
290                 EOF
291         fi
292 }
293
294 # check arguments for an option that would cause mysqld to stop
295 # return true if there is one
296 _mysql_want_help() {
297         local arg
298         for arg; do
299                 case "$arg" in
300                         -'?'|--help|--print-defaults|-V|--version)
301                                 return 0
302                                 ;;
303                 esac
304         done
305         return 1
306 }
307
308 _main() {
309         # if command starts with an option, prepend mysqld
310         if [ "${1:0:1}" = '-' ]; then
311                 set -- mysqld "$@"
312         fi
313
314         # skip setup if they aren't running mysqld or want an option that stops mysqld
315         if [ "$1" = 'mysqld' ] && ! _mysql_want_help "$@"; then
316                 mysql_note "Entrypoint script for MySQL Server ${MARIADB_VERSION} started."
317
318                 mysql_check_config "$@"
319                 # Load various environment variables
320                 docker_setup_env "$@"
321                 docker_create_db_directories
322
323                 # If container is started as root user, restart as dedicated mysql user
324                 if [ "$(id -u)" = "0" ]; then
325                         mysql_note "Switching to dedicated user 'mysql'"
326                         exec gosu mysql "$BASH_SOURCE" "$@"
327                 fi
328
329                 # there's no database, so it needs to be initialized
330                 if [ -z "$DATABASE_ALREADY_EXISTS" ]; then
331                         docker_verify_minimum_env
332
333                         # check dir permissions to reduce likelihood of half-initialized database
334                         ls /docker-entrypoint-initdb.d/ > /dev/null
335
336                         docker_init_database_dir "$@"
337
338                         mysql_note "Starting temporary server"
339                         docker_temp_server_start "$@"
340                         mysql_note "Temporary server started."
341
342                         docker_setup_db
343                         docker_process_init_files /docker-entrypoint-initdb.d/*
344
345                         for i in $(echo $PORTAL_DB_TABLES | sed "s/,/ /g")
346                                 do
347                                         echo "Granting portal user ALL PRIVILEGES for table $i"
348                                         echo "GRANT ALL ON \`$i\`.* TO '$MYSQL_USER'@'%' ;" | "${mysql[@]}"
349                                 done
350
351                         mysql_note "Stopping temporary server"
352                         docker_temp_server_stop
353                         mysql_note "Temporary server stopped"
354
355                         echo
356                         mysql_note "MySQL init process done. Ready for start up."
357                         echo
358                 fi
359         fi
360         exec "$@"
361 }
362
363 # If we are sourced from elsewhere, don't perform any further actions
364 if ! _is_sourced; then
365         _main "$@"
366 fi