From 27f2722bb2ebd26354321adc8c3f19f7958529e1 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Wed, 15 Mar 2017 14:04:54 -0700 Subject: [PATCH] Add "docker-entrypoint-initdb.d" behavior which mimics PostgreSQL, including (optional) automated "root" user creation --- 3.0/Dockerfile | 13 +++-- 3.0/docker-entrypoint.sh | 99 +++++++++++++++++++++++++++++++++++++ 3.2/Dockerfile | 10 ++-- 3.2/docker-entrypoint.sh | 104 +++++++++++++++++++++++++++++++++++++++ 3.4/Dockerfile | 10 ++-- 3.4/docker-entrypoint.sh | 99 +++++++++++++++++++++++++++++++++++++ 6 files changed, 326 insertions(+), 9 deletions(-) diff --git a/3.0/Dockerfile b/3.0/Dockerfile index ea375f03c9..c188f27178 100644 --- a/3.0/Dockerfile +++ b/3.0/Dockerfile @@ -1,10 +1,14 @@ -FROM debian:wheezy +FROM debian:wheezy-slim # add our user and group first to make sure their IDs get assigned consistently, regardless of whatever dependencies get added RUN groupadd -r mongodb && useradd -r -g mongodb mongodb +# add "wheezy-backports" for "jq" +RUN echo 'deb http://deb.debian.org/debian wheezy-backports main' > /etc/apt/sources.list.d/backports.list + RUN apt-get update \ && apt-get install -y --no-install-recommends \ + jq \ numactl \ && rm -rf /var/lib/apt/lists/* @@ -22,6 +26,8 @@ RUN set -x \ && gosu nobody true \ && apt-get purge -y --auto-remove ca-certificates wget +RUN mkdir /docker-entrypoint-initdb.d + ENV GPG_KEYS \ # gpg: key 7F0CEB10: public key "Richard Kreuter " imported 492EAFE8CD016A07919F1D2B9ECBEC467F0CEB10 @@ -56,8 +62,9 @@ RUN mkdir -p /data/db /data/configdb \ && chown -R mongodb:mongodb /data/db /data/configdb VOLUME /data/db /data/configdb -COPY docker-entrypoint.sh /entrypoint.sh -ENTRYPOINT ["/entrypoint.sh"] +COPY docker-entrypoint.sh /usr/local/bin/ +RUN ln -s usr/local/bin/docker-entrypoint.sh /entrypoint.sh # backwards compat +ENTRYPOINT ["docker-entrypoint.sh"] EXPOSE 27017 CMD ["mongod"] diff --git a/3.0/docker-entrypoint.sh b/3.0/docker-entrypoint.sh index acae0e272f..b2c38299c8 100755 --- a/3.0/docker-entrypoint.sh +++ b/3.0/docker-entrypoint.sh @@ -11,6 +11,7 @@ if [[ "$1" == mongo* ]] && [ "$(id -u)" = '0' ]; then if [ "$1" = 'mongod' ]; then chown -R mongodb /data/configdb /data/db fi + chown --dereference mongodb /dev/stdout /dev/stderr exec gosu mongodb "$BASH_SOURCE" "$@" fi @@ -23,4 +24,102 @@ if [[ "$1" == mongo* ]]; then fi fi +# 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 + echo >&2 "error: both $var and $fileVar are set (but are exclusive)" + exit 1 + fi + local val="$def" + if [ "${!var:-}" ]; then + val="${!var}" + elif [ "${!fileVar:-}" ]; then + val="$(< "${!fileVar}")" + fi + export "$var"="$val" + unset "$fileVar" +} + +if [ "$1" = 'mongod' ]; then + file_env 'MONGO_INITDB_ROOT_USERNAME' + file_env 'MONGO_INITDB_ROOT_PASSWORD' + if [ "$MONGO_INITDB_ROOT_USERNAME" ] && [ "$MONGO_INITDB_ROOT_PASSWORD" ]; then + set -- "$@" --auth + fi + + # check for a few known paths (to determine whether we've already initialized and should thus skip our initdb scripts) + definitelyAlreadyInitialized= + for path in \ + /data/db/WiredTiger \ + /data/db/journal \ + /data/db/local.0 \ + /data/db/storage.bson \ + ; do + if [ -e "$path" ]; then + definitelyAlreadyInitialized="$path" + break + fi + done + + if [ -z "$definitelyAlreadyInitialized" ]; then + "$@" --fork --bind_ip 127.0.0.1 --logpath "/proc/$$/fd/1" + + mongo=( mongo --quiet ) + + # check to see that "mongod" actually did start up (catches "--help", "--version", MongoDB 3.2 being silly, etc) + if ! "${mongo[@]}" 'admin' --eval 'quit(0)' &> /dev/null; then + echo >&2 + echo >&2 'error: mongod does not appear to have started up -- perhaps it had an error?' + echo >&2 + exit 1 + fi + + if [ "$MONGO_INITDB_ROOT_USERNAME" ] && [ "$MONGO_INITDB_ROOT_PASSWORD" ]; then + rootAuthDatabase='admin' + + "${mongo[@]}" "$rootAuthDatabase" <<-EOJS + db.createUser({ + user: $(jq --arg 'user' "$MONGO_INITDB_ROOT_USERNAME" --null-input '$user'), + pwd: $(jq --arg 'pwd' "$MONGO_INITDB_ROOT_PASSWORD" --null-input '$pwd'), + roles: [ { role: 'root', db: $(jq --arg 'db' "$rootAuthDatabase" --null-input '$db') } ] + }) + EOJS + + mongo+=( + --username="$MONGO_INITDB_ROOT_USERNAME" + --password="$MONGO_INITDB_ROOT_PASSWORD" + --authenticationDatabase="$rootAuthDatabase" + ) + fi + + export MONGO_INITDB_DATABASE="${MONGO_INITDB_DATABASE:-test}" + + echo + for f in /docker-entrypoint-initdb.d/*; do + case "$f" in + *.sh) echo "$0: running $f"; . "$f" ;; + *.js) echo "$0: running $f"; "${mongo[@]}" "$MONGO_INITDB_DATABASE" "$f"; echo ;; + *) echo "$0: ignoring $f" ;; + esac + echo + done + + "$@" --shutdown + + echo + echo 'MongoDB init process complete; ready for start up.' + echo + fi + + unset MONGO_INITDB_ROOT_USERNAME + unset MONGO_INITDB_ROOT_PASSWORD + unset MONGO_INITDB_DATABASE +fi + exec "$@" diff --git a/3.2/Dockerfile b/3.2/Dockerfile index a79570973f..52d67772b7 100644 --- a/3.2/Dockerfile +++ b/3.2/Dockerfile @@ -1,10 +1,11 @@ -FROM debian:jessie +FROM debian:jessie-slim # add our user and group first to make sure their IDs get assigned consistently, regardless of whatever dependencies get added RUN groupadd -r mongodb && useradd -r -g mongodb mongodb RUN apt-get update \ && apt-get install -y --no-install-recommends \ + jq \ numactl \ && rm -rf /var/lib/apt/lists/* @@ -22,6 +23,8 @@ RUN set -x \ && gosu nobody true \ && apt-get purge -y --auto-remove ca-certificates wget +RUN mkdir /docker-entrypoint-initdb.d + ENV GPG_KEYS \ # pub 4096R/AAB2461C 2014-02-25 [expires: 2016-02-25] # Key fingerprint = DFFA 3DCF 326E 302C 4787 673A 01C4 E7FA AAB2 461C @@ -62,8 +65,9 @@ RUN mkdir -p /data/db /data/configdb \ && chown -R mongodb:mongodb /data/db /data/configdb VOLUME /data/db /data/configdb -COPY docker-entrypoint.sh /entrypoint.sh -ENTRYPOINT ["/entrypoint.sh"] +COPY docker-entrypoint.sh /usr/local/bin/ +RUN ln -s usr/local/bin/docker-entrypoint.sh /entrypoint.sh # backwards compat +ENTRYPOINT ["docker-entrypoint.sh"] EXPOSE 27017 CMD ["mongod"] diff --git a/3.2/docker-entrypoint.sh b/3.2/docker-entrypoint.sh index acae0e272f..e7efe6a881 100755 --- a/3.2/docker-entrypoint.sh +++ b/3.2/docker-entrypoint.sh @@ -11,6 +11,7 @@ if [[ "$1" == mongo* ]] && [ "$(id -u)" = '0' ]; then if [ "$1" = 'mongod' ]; then chown -R mongodb /data/configdb /data/db fi + chown --dereference mongodb /dev/stdout /dev/stderr exec gosu mongodb "$BASH_SOURCE" "$@" fi @@ -23,4 +24,107 @@ if [[ "$1" == mongo* ]]; then fi fi +# 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 + echo >&2 "error: both $var and $fileVar are set (but are exclusive)" + exit 1 + fi + local val="$def" + if [ "${!var:-}" ]; then + val="${!var}" + elif [ "${!fileVar:-}" ]; then + val="$(< "${!fileVar}")" + fi + export "$var"="$val" + unset "$fileVar" +} + +if [ "$1" = 'mongod' ]; then + file_env 'MONGO_INITDB_ROOT_USERNAME' + file_env 'MONGO_INITDB_ROOT_PASSWORD' + if [ "$MONGO_INITDB_ROOT_USERNAME" ] && [ "$MONGO_INITDB_ROOT_PASSWORD" ]; then + set -- "$@" --auth + fi + + # check for a few known paths (to determine whether we've already initialized and should thus skip our initdb scripts) + definitelyAlreadyInitialized= + for path in \ + /data/db/WiredTiger \ + /data/db/journal \ + /data/db/local.0 \ + /data/db/storage.bson \ + ; do + if [ -e "$path" ]; then + definitelyAlreadyInitialized="$path" + break + fi + done + + if [ -z "$definitelyAlreadyInitialized" ]; then + "$@" --fork --bind_ip 127.0.0.1 --logpath "/proc/$$/fd/1" + + mongo=( mongo --quiet ) + + # check to see that "mongod" actually did start up (catches "--help", "--version", MongoDB 3.2 being silly, etc) + if ! "${mongo[@]}" 'admin' --eval 'quit(0)' &> /dev/null; then + # TODO figure out why only MongoDB 3.2 seems to exit from "--fork" before it's actually listening + # (adding "sleep 0.01" was sufficient on Tianon's local box, hence this tiny "retry up to one time") + sleep 5 + fi + if ! "${mongo[@]}" 'admin' --eval 'quit(0)' &> /dev/null; then + echo >&2 + echo >&2 'error: mongod does not appear to have started up -- perhaps it had an error?' + echo >&2 + exit 1 + fi + + if [ "$MONGO_INITDB_ROOT_USERNAME" ] && [ "$MONGO_INITDB_ROOT_PASSWORD" ]; then + rootAuthDatabase='admin' + + "${mongo[@]}" "$rootAuthDatabase" <<-EOJS + db.createUser({ + user: $(jq --arg 'user' "$MONGO_INITDB_ROOT_USERNAME" --null-input '$user'), + pwd: $(jq --arg 'pwd' "$MONGO_INITDB_ROOT_PASSWORD" --null-input '$pwd'), + roles: [ { role: 'root', db: $(jq --arg 'db' "$rootAuthDatabase" --null-input '$db') } ] + }) + EOJS + + mongo+=( + --username="$MONGO_INITDB_ROOT_USERNAME" + --password="$MONGO_INITDB_ROOT_PASSWORD" + --authenticationDatabase="$rootAuthDatabase" + ) + fi + + export MONGO_INITDB_DATABASE="${MONGO_INITDB_DATABASE:-test}" + + echo + for f in /docker-entrypoint-initdb.d/*; do + case "$f" in + *.sh) echo "$0: running $f"; . "$f" ;; + *.js) echo "$0: running $f"; "${mongo[@]}" "$MONGO_INITDB_DATABASE" "$f"; echo ;; + *) echo "$0: ignoring $f" ;; + esac + echo + done + + "$@" --shutdown + + echo + echo 'MongoDB init process complete; ready for start up.' + echo + fi + + unset MONGO_INITDB_ROOT_USERNAME + unset MONGO_INITDB_ROOT_PASSWORD + unset MONGO_INITDB_DATABASE +fi + exec "$@" diff --git a/3.4/Dockerfile b/3.4/Dockerfile index 1560876056..f1680aa9ad 100644 --- a/3.4/Dockerfile +++ b/3.4/Dockerfile @@ -1,10 +1,11 @@ -FROM debian:jessie +FROM debian:jessie-slim # add our user and group first to make sure their IDs get assigned consistently, regardless of whatever dependencies get added RUN groupadd -r mongodb && useradd -r -g mongodb mongodb RUN apt-get update \ && apt-get install -y --no-install-recommends \ + jq \ numactl \ && rm -rf /var/lib/apt/lists/* @@ -22,6 +23,8 @@ RUN set -x \ && gosu nobody true \ && apt-get purge -y --auto-remove ca-certificates wget +RUN mkdir /docker-entrypoint-initdb.d + ENV GPG_KEYS \ # pub 4096R/A15703C6 2016-01-11 [expires: 2018-01-10] # Key fingerprint = 0C49 F373 0359 A145 1858 5931 BC71 1F9B A157 03C6 @@ -58,8 +61,9 @@ RUN mkdir -p /data/db /data/configdb \ && chown -R mongodb:mongodb /data/db /data/configdb VOLUME /data/db /data/configdb -COPY docker-entrypoint.sh /entrypoint.sh -ENTRYPOINT ["/entrypoint.sh"] +COPY docker-entrypoint.sh /usr/local/bin/ +RUN ln -s usr/local/bin/docker-entrypoint.sh /entrypoint.sh # backwards compat +ENTRYPOINT ["docker-entrypoint.sh"] EXPOSE 27017 CMD ["mongod"] diff --git a/3.4/docker-entrypoint.sh b/3.4/docker-entrypoint.sh index acae0e272f..b2c38299c8 100755 --- a/3.4/docker-entrypoint.sh +++ b/3.4/docker-entrypoint.sh @@ -11,6 +11,7 @@ if [[ "$1" == mongo* ]] && [ "$(id -u)" = '0' ]; then if [ "$1" = 'mongod' ]; then chown -R mongodb /data/configdb /data/db fi + chown --dereference mongodb /dev/stdout /dev/stderr exec gosu mongodb "$BASH_SOURCE" "$@" fi @@ -23,4 +24,102 @@ if [[ "$1" == mongo* ]]; then fi fi +# 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 + echo >&2 "error: both $var and $fileVar are set (but are exclusive)" + exit 1 + fi + local val="$def" + if [ "${!var:-}" ]; then + val="${!var}" + elif [ "${!fileVar:-}" ]; then + val="$(< "${!fileVar}")" + fi + export "$var"="$val" + unset "$fileVar" +} + +if [ "$1" = 'mongod' ]; then + file_env 'MONGO_INITDB_ROOT_USERNAME' + file_env 'MONGO_INITDB_ROOT_PASSWORD' + if [ "$MONGO_INITDB_ROOT_USERNAME" ] && [ "$MONGO_INITDB_ROOT_PASSWORD" ]; then + set -- "$@" --auth + fi + + # check for a few known paths (to determine whether we've already initialized and should thus skip our initdb scripts) + definitelyAlreadyInitialized= + for path in \ + /data/db/WiredTiger \ + /data/db/journal \ + /data/db/local.0 \ + /data/db/storage.bson \ + ; do + if [ -e "$path" ]; then + definitelyAlreadyInitialized="$path" + break + fi + done + + if [ -z "$definitelyAlreadyInitialized" ]; then + "$@" --fork --bind_ip 127.0.0.1 --logpath "/proc/$$/fd/1" + + mongo=( mongo --quiet ) + + # check to see that "mongod" actually did start up (catches "--help", "--version", MongoDB 3.2 being silly, etc) + if ! "${mongo[@]}" 'admin' --eval 'quit(0)' &> /dev/null; then + echo >&2 + echo >&2 'error: mongod does not appear to have started up -- perhaps it had an error?' + echo >&2 + exit 1 + fi + + if [ "$MONGO_INITDB_ROOT_USERNAME" ] && [ "$MONGO_INITDB_ROOT_PASSWORD" ]; then + rootAuthDatabase='admin' + + "${mongo[@]}" "$rootAuthDatabase" <<-EOJS + db.createUser({ + user: $(jq --arg 'user' "$MONGO_INITDB_ROOT_USERNAME" --null-input '$user'), + pwd: $(jq --arg 'pwd' "$MONGO_INITDB_ROOT_PASSWORD" --null-input '$pwd'), + roles: [ { role: 'root', db: $(jq --arg 'db' "$rootAuthDatabase" --null-input '$db') } ] + }) + EOJS + + mongo+=( + --username="$MONGO_INITDB_ROOT_USERNAME" + --password="$MONGO_INITDB_ROOT_PASSWORD" + --authenticationDatabase="$rootAuthDatabase" + ) + fi + + export MONGO_INITDB_DATABASE="${MONGO_INITDB_DATABASE:-test}" + + echo + for f in /docker-entrypoint-initdb.d/*; do + case "$f" in + *.sh) echo "$0: running $f"; . "$f" ;; + *.js) echo "$0: running $f"; "${mongo[@]}" "$MONGO_INITDB_DATABASE" "$f"; echo ;; + *) echo "$0: ignoring $f" ;; + esac + echo + done + + "$@" --shutdown + + echo + echo 'MongoDB init process complete; ready for start up.' + echo + fi + + unset MONGO_INITDB_ROOT_USERNAME + unset MONGO_INITDB_ROOT_PASSWORD + unset MONGO_INITDB_DATABASE +fi + exec "$@"