diff --git a/5.5/Dockerfile b/5.5/Dockerfile index a0dcd993..a386f2c9 100644 --- a/5.5/Dockerfile +++ b/5.5/Dockerfile @@ -3,7 +3,7 @@ FROM centos:centos7 # MySQL image for OpenShift. # # Volumes: -# * /var/lib/mysql - Datastore for MySQL +# * /var/lib/mysql/data - Datastore for MySQL # Environment: # * $MYSQL_USER - Database user name # * $MYSQL_PASSWORD - User's password @@ -26,14 +26,16 @@ RUN rpmkeys --import file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 && \ yum -y --setopt=tsflags=nodocs install https://www.softwarecollections.org/en/scls/rhscl/mysql55/epel-7-x86_64/download/rhscl-mysql55-epel-7-x86_64.noarch.rpm && \ yum -y --setopt=tsflags=nodocs install hostname mysql55 && \ yum clean all && \ - mkdir -p /var/lib/mysql && chown mysql.mysql /var/lib/mysql && \ + mkdir -p /var/lib/mysql/data && chown mysql.mysql /var/lib/mysql/data && \ test "$(id mysql)" = "uid=27(mysql) gid=27(mysql) groups=27(mysql)" COPY run-mysqld.sh /usr/local/bin/ -COPY contrib /opt/openshift/ +COPY contrib/.bashrc /var/lib/mysql/ +COPY contrib/etc /opt/openshift/etc/ -VOLUME ["/var/lib/mysql"] +VOLUME ["/var/lib/mysql/data"] USER mysql ENTRYPOINT ["run-mysqld.sh"] +CMD ["mysqld"] diff --git a/5.5/Dockerfile.rhel7 b/5.5/Dockerfile.rhel7 index 5438be79..7a7f423b 100644 --- a/5.5/Dockerfile.rhel7 +++ b/5.5/Dockerfile.rhel7 @@ -3,7 +3,7 @@ FROM rhel7 # MySQL image for OpenShift. # # Volumes: -# * /var/lib/mysql - Datastore for MySQL +# * /var/lib/mysql/data - Datastore for MySQL # Environment: # * $MYSQL_USER - Database user name # * $MYSQL_PASSWORD - User's password @@ -27,14 +27,16 @@ RUN yum install -y yum-utils hostname && \ yum-config-manager --enable rhel-7-server-optional-rpms && \ yum install -y --setopt=tsflags=nodocs mysql55 && \ yum clean all && \ - mkdir -p /var/lib/mysql && chown mysql.mysql /var/lib/mysql && \ + mkdir -p /var/lib/mysql/data && chown mysql.mysql /var/lib/mysql/data && \ test "$(id mysql)" = "uid=27(mysql) gid=27(mysql) groups=27(mysql)" COPY run-mysqld.sh /usr/local/bin/ -COPY contrib /opt/openshift/ +COPY contrib/.bashrc /var/lib/mysql/ +COPY contrib/etc /opt/openshift/etc/ -VOLUME ["/var/lib/mysql"] +VOLUME ["/var/lib/mysql/data"] USER mysql ENTRYPOINT ["run-mysqld.sh"] +CMD ["mysqld"] diff --git a/5.5/contrib/.bashrc b/5.5/contrib/.bashrc new file mode 100755 index 00000000..1d44f536 --- /dev/null +++ b/5.5/contrib/.bashrc @@ -0,0 +1,2 @@ +# This will make scl collection binaries work out of box. +source scl_source enable mysql55 diff --git a/5.5/contrib/etc/my.cnf b/5.5/contrib/etc/my.cnf index aab51c95..a4325b20 100644 --- a/5.5/contrib/etc/my.cnf +++ b/5.5/contrib/etc/my.cnf @@ -1,7 +1,7 @@ [mysqld] user=mysql -datadir=/var/lib/mysql +datadir=/var/lib/mysql/data basedir=/opt/rh/mysql55/root/usr plugin-dir=/opt/rh/mysql55/root/usr/lib64/mysql/plugin diff --git a/5.5/run-mysqld.sh b/5.5/run-mysqld.sh index 9fca51a2..33cad11a 100755 --- a/5.5/run-mysqld.sh +++ b/5.5/run-mysqld.sh @@ -1,11 +1,25 @@ -#!/bin/bash -e +#!/bin/bash + +# For SCL enablement +source $HOME/.bashrc + +set -eu + +# Data directory where MySQL database files live. The data subdirectory is here +# because .bashrc lives in /var/lib/mysql/ and we don't want a volume to +# override it. +MYSQL_DATADIR=/var/lib/mysql/data +MYSQL_DEFAULTS_FILE=/opt/openshift/etc/my.cnf # Be paranoid and stricter than we should be. # https://dev.mysql.com/doc/refman/5.5/en/identifiers.html mysql_identifier_regex='^[a-zA-Z0-9_]+$' mysql_password_regex='^[a-zA-Z0-9_~!@#$%^&*()-=<>,.?;:|]+$' -function usage { +function usage() { + if [ $# == 2 ]; then + echo "error: $1" + fi echo "You must specify following environment variables:" echo " \$MYSQL_USER (regex: '$mysql_identifier_regex')" echo " \$MYSQL_PASSWORD (regex: '$mysql_password_regex')" @@ -15,38 +29,24 @@ function usage { exit 1 } -function valid_mysql_identifier { - local var="$1" ; shift - [[ "${var}" =~ $mysql_identifier_regex ]] -} - -function valid_mysql_password { - local var="$1" ; shift - [[ "${var}" =~ $mysql_password_regex ]] +function check_env_vars() { + [[ "$MYSQL_USER" =~ $mysql_identifier_regex ]] || usage "Invalid MySQL username" + [ ${#MYSQL_USER} -le 16 ] || usage "MySQL username too long (maximum 16 characters)" + [[ "$MYSQL_PASSWORD" =~ $mysql_password_regex ]] || usage "Invalid password" + [[ "$MYSQL_DATABASE" =~ $mysql_identifier_regex ]] || usage "Invalid database name" + [ ${#MYSQL_DATABASE} -le 64 ] || usage "Database name too long (maximum 64 characters)" + if [ -v MYSQL_ROOT_PASSWORD ]; then + [[ "$MYSQL_ROOT_PASSWORD" =~ $mysql_password_regex ]] || usage "Invalid root password" + fi } -valid_mysql_identifier "$MYSQL_USER" || usage -valid_mysql_password "$MYSQL_PASSWORD" || usage -valid_mysql_identifier "$MYSQL_DATABASE" || usage - # Make sure env variables don't propagate to mysqld process. -mysql_user="$MYSQL_USER" ; unset MYSQL_USER -mysql_pass="$MYSQL_PASSWORD" ; unset MYSQL_PASSWORD -mysql_db="$MYSQL_DATABASE" ; unset MYSQL_DATABASE - -# Root password. -if [ "$MYSQL_ROOT_PASSWORD" ]; then - valid_mysql_password "$MYSQL_ROOT_PASSWORD" || usage - root_pass="$MYSQL_ROOT_PASSWORD" -fi -unset MYSQL_ROOT_PASSWORD - -# SCL in CentOS/RHEL 7 doesn't support --exec, we need to do it ourselves -# The '|| exit 1' is here so -e doesn't propagate into scl_source. -source scl_source enable mysql55 || exit 1 +function unset_env_vars() { + unset MYSQL_USER MYSQL_PASSWORD MYSQL_DATABASE MYSQL_ROOT_PASSWORD +} # Poll until MySQL responds to our ping. -function wait_for_mysql { +function wait_for_mysql() { pid=$1 ; shift while [ true ]; do @@ -60,39 +60,47 @@ function wait_for_mysql { done } -if [ ! -d '/var/lib/mysql/mysql' ]; then +if [ "$1" = "mysqld" -a ! -d "$MYSQL_DATADIR/mysql" ]; then + + shift + check_env_vars echo 'Running mysql_install_db' - mysql_install_db --datadir=/var/lib/mysql + mysql_install_db --datadir=$MYSQL_DATADIR # Now start mysqld and add appropriate users. echo 'Starting mysqld to create users' /opt/rh/mysql55/root/usr/libexec/mysqld \ - --defaults-file=/opt/openshift/etc/my.cnf \ + --defaults-file=$MYSQL_DEFAULTS_FILE \ --skip-networking --socket=/tmp/mysql.sock & mysql_pid=$! wait_for_mysql $mysql_pid # Set common flags. mysql_flags="-u root --socket=/tmp/mysql.sock" - admin_flags="--defaults-file=/opt/openshift/etc/my.cnf $mysql_flags" + admin_flags="--defaults-file=$MYSQL_DEFAULTS_FILE $mysql_flags" mysqladmin $admin_flags -f drop test - mysqladmin $admin_flags create "${mysql_db}" + mysqladmin $admin_flags create "${MYSQL_DATABASE}" mysql $mysql_flags <<-EOSQL - CREATE USER '${mysql_user}'@'%' IDENTIFIED BY '${mysql_pass}'; - GRANT ALL ON \`${mysql_db}\`.* TO '${mysql_user}'@'%' ; + CREATE USER '${MYSQL_USER}'@'%' IDENTIFIED BY '${MYSQL_PASSWORD}'; + GRANT ALL ON \`${MYSQL_DATABASE}\`.* TO '${MYSQL_USER}'@'%' ; FLUSH PRIVILEGES ; EOSQL - if [ -v root_pass ]; then + if [ -v MYSQL_ROOT_PASSWORD ]; then mysql $mysql_flags <<-EOSQL - GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '${root_pass}'; + GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}'; EOSQL fi mysqladmin $admin_flags flush-privileges shutdown + + unset_env_vars + + exec /opt/rh/mysql55/root/usr/libexec/mysqld \ + --defaults-file=$MYSQL_DEFAULTS_FILE \ + "$@" 2>&1 fi -exec /opt/rh/mysql55/root/usr/libexec/mysqld \ - --defaults-file=/opt/openshift/etc/my.cnf \ - "$@" 2>&1 +unset_env_vars +exec "$@" diff --git a/5.5/test/run b/5.5/test/run index b1928505..b9dd3e36 100755 --- a/5.5/test/run +++ b/5.5/test/run @@ -13,7 +13,7 @@ IMAGE_NAME=${IMAGE_NAME-openshift/mysql-55-centos7-candidate} CIDFILE_DIR=$(mktemp --suffix=mysql_test_cidfiles -d) -cleanup() { +function cleanup() { for cidfile in $CIDFILE_DIR/* ; do CONTAINER=$(cat $cidfile) @@ -32,21 +32,21 @@ cleanup() { } trap cleanup EXIT SIGINT -get_cid () { +function get_cid() { local id="$1" ; shift || return 1 echo $(cat "$CIDFILE_DIR/$id") } -get_container_ip() { +function get_container_ip() { local id="$1" ; shift docker inspect --format='{{.NetworkSettings.IPAddress}}' $(get_cid "$id") } -mysql_cmd() { - docker run --rm --entrypoint=scl $IMAGE_NAME enable mysql55 -- mysql --host $CONTAINER_IP -u$USER -p"$PASS" "$@" db +function mysql_cmd() { + docker run --rm $IMAGE_NAME mysql --host $CONTAINER_IP -u$USER -p"$PASS" "$@" db } -test_connection() { +function test_connection() { local name=$1 ; shift ip=$(get_container_ip $name) echo " Testing MySQL connection to $ip..." @@ -69,7 +69,7 @@ test_connection() { return 1 } -test_mysql() { +function test_mysql() { echo " Testing MySQL" mysql_cmd <<< "CREATE TABLE tbl (col1 VARCHAR(20), col2 VARCHAR(20));" mysql_cmd <<< "INSERT INTO tbl VALUES ('foo1', 'bar1');" @@ -80,7 +80,7 @@ test_mysql() { echo " Success!" } -create_container() { +function create_container() { local name=$1 ; shift cidfile="$CIDFILE_DIR/$name" # create container with a cidfile in a directory for cleanup @@ -88,19 +88,58 @@ create_container() { echo "Created container $(cat $cidfile)" } -assert_login_access() { +function assert_login_access() { local USER=$1 ; shift local PASS=$1 ; shift - local SUCCESS=$1 ; shift + local success=$1 ; shift - if $SUCCESS; then + if $success; then mysql_cmd <<< "SELECT 1;" && echo " $USER($PASS) access granted as expected" else mysql_cmd <<< "SELECT 1;" || echo " $USER($PASS) access denied as expected" fi } -run_tests() { +# Make sure the invocation of docker run fails. +function assert_container_creation_fails() { + + # Time the docker run command. It should fail. If it doesn't fail, + # mysqld will keep running so we kill it with SIGKILL to make sure + # timeout returns a non-zero value. + set +e + timeout -s 9 --preserve-status 60s docker run --rm "$@" $IMAGE_NAME + ret=$? + set -e + + # Timeout will exit with a high number. + if [ $ret -gt 30 ]; then + return 1 + fi +} + +function try_image_invalid_combinations() { + assert_container_creation_fails "$@" + assert_container_creation_fails -e MYSQL_USER=user -e MYSQL_PASSWORD=pass "$@" + assert_container_creation_fails -e MYSQL_USER=user -e MYSQL_DATABASE=db "$@" + assert_container_creation_fails -e MYSQL_PASSWORD=pass -e MYSQL_DATABASE=db "$@" +} + +function run_container_creation_tests() { + echo " Testing image entrypoint usage" + try_image_invalid_combinations + try_image_invalid_combinations -e MYSQL_ROOT_PASSWORD=root_pass + + VERY_LONG_DB_NAME="very_long_database_name_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + assert_container_creation_fails -e MYSQL_USER=\$invalid -e MYSQL_PASSWORD=pass -e MYSQL_DATABASE=db -e MYSQL_ROOT_PASSWORD=root_pass + assert_container_creation_fails -e MYSQL_USER=very_long_username -e MYSQL_PASSWORD=pass -e MYSQL_DATABASE=db -e MYSQL_ROOT_PASSWORD=root_pass + assert_container_creation_fails -e MYSQL_USER=user -e MYSQL_PASSWORD="\"" -e MYSQL_DATABASE=db -e MYSQL_ROOT_PASSWORD=root_pass + assert_container_creation_fails -e MYSQL_USER=user -e MYSQL_PASSWORD=pass -e MYSQL_DATABASE=\$invalid -e MYSQL_ROOT_PASSWORD=root_pass + assert_container_creation_fails -e MYSQL_USER=user -e MYSQL_PASSWORD=pass -e MYSQL_DATABASE=$VERY_LONG_DB_NAME -e MYSQL_ROOT_PASSWORD=root_pass + assert_container_creation_fails -e MYSQL_USER=user -e MYSQL_PASSWORD=pass -e MYSQL_DATABASE=db -e MYSQL_ROOT_PASSWORD="\"" + echo " Success!" +} + +function run_tests() { local name=$1 ; shift envs="-e MYSQL_USER=$USER -e MYSQL_PASSWORD=$PASS -e MYSQL_DATABASE=db" if [ -v ROOT_PASS ]; then @@ -125,5 +164,6 @@ run_tests() { # Tests. +run_container_creation_tests USER=user PASS=pass run_tests no_root USER=user1 PASS=pass1 ROOT_PASS=r00t run_tests root diff --git a/README.md b/README.md index f0521f43..1b502d02 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,13 @@ # OpenShift MySQL image -This repository contains Dockerfiles for MySQL images for OpenShift. -Users can choose between RHEL and CentOS based images. +This repository contains Dockerfiles for MySQL images for OpenShift. Users can choose between RHEL and CentOS based images. # Installation and Usage Choose between CentOS7 or RHEL7 based image: * **RHEL7 based image** -To build a rhel7-based image, you need to run Docker build on a properly -subscribed RHEL machine. +To build a RHEL7-based image, you need to run Docker build on a properly subscribed RHEL machine. ```console git clone https://github.com/openshift/mysql.git @@ -27,9 +25,7 @@ make build ## Environment variables and volumes -The image recognizes following environment variables that you can set -during initialization, by passing `-e VAR=VALUE` to the Docker run -command. +The image recognizes following environment variables that you can set during initialization, by passing `-e VAR=VALUE` to the Docker run command. | Variable name | Description | | :--------------------- | ----------------------------------------- | @@ -38,49 +34,30 @@ command. | `MYSQL_DATABASE` | Database name | | `MYSQL_ROOT_PASSWORD` | Password for the root user (optional) | -You can also set following mount points by passing `-v /host:/container` -flag to docker. +You can also set following mount points by passing `-v /host:/container` flag to docker. -| Volume mount point | Description | -| :------------------ | -------------------- | -| `/var/lib/mysql` | MySQL data directory | +| Volume mount point | Description | +| :----------------------- | -------------------- | +| `/var/lib/mysql/data` | MySQL data directory | ## Usage -We will assume that you are using the `openshift/mysql-55-centos7` -image. Supposing that you want to set only mandatory required environment -variables and store the database on in the `/home/user/database` -directory on the host filesystem, you need to execute the following -command: +We will assume that you are using the `openshift/mysql-55-centos7` image. Supposing that you want to set only mandatory environment variables and not store the database directory on the host filesystem, you need to execute the following command: ```console -docker run -d -e MYSQL_USER= -e MYSQL_PASSWORD= -e MYSQL_DATABASE= -v /home/user/database:/var/lib/mysql openshift/mysql-55-centos7 +docker run -d --name mysql_database -e MYSQL_USER=user -e MYSQL_PASSWORD=pass -e MYSQL_DATABASE=db -p 3306:3306 openshift/mysql-55-centos7 ``` -If the database directory is not initialized, the entrypoint script will -first run `mysql_install_db` and setup necessary database users and -passwords. After the database is initialized, or if it was already -present, `mysqld` is executed and will run as PID 1. You can stop the -detached container by running `docker stop `. +This will create a container named `mysql_database` running MySQL with database `db` and user with credentials `user:pass`. Port 3306 will be exposed and mapped to host. If you want your database to be persistent across container executions, also add a `-v /host/db/path:/var/lib/mysql/data` argument. This is going to be the MySQL data directory. + +If the database directory is not initialized, the entrypoint script will first run [`mysql_install_db`](https://dev.mysql.com/doc/refman/5.5/en/mysql-install-db.html) and setup necessary database users and passwords. After the database is initialized, or if it was already present, `mysqld` is executed and will run as PID 1. You can stop the detached container by running `docker stop mysql_database`. ### MySQL root user -The root user has no password set by default, only allowing local -connections. You can set it by setting `MYSQL_ROOT_PASSWORD` environment -variable when initializing your container. This will allow you to login -to the root account remotely. Local connections will still not require -password. +The root user has no password set by default, only allowing local connections. You can set it by setting `MYSQL_ROOT_PASSWORD` environment variable when initializing your container. This will allow you to login to the root account remotely. Local connections will still not require password. ## Software Collections -We use [Software Collections](https://www.softwarecollections.org/) to -install and launch MySQL. If you want to execute a command inside of a -running container (for debugging for example), you need to prefix it -with `scl enable` command. Some examples: +We use [Software Collections](https://www.softwarecollections.org/) to install and launch MySQL. Any command run by the entrypoint will have environment set up properly, so you shouldn't worry. However, if you want to execute a command inside of a running container (for debugging for example), you need to prefix it with `scl enable ` command. In the case of MySQL 5.5, the collection name will be "mysql55": ```console -# Running mysql commands inside the container -scl enable mysql55 -- mysql -uuser -p - -# Executing a command inside a running container from host -# Note: You will be able to run mysql commands without invoking the scl commands -docker exec -ti scl enable mysql55 /bin/bash +docker exec -ti mysql_database scl enable mysql55 -- mysql -h 127.0.0.1 -uuser -ppass ``` diff --git a/hack/build.sh b/hack/build.sh index 7dbcafbc..39e24b76 100755 --- a/hack/build.sh +++ b/hack/build.sh @@ -10,7 +10,7 @@ OS=$1 VERSION=$2 # TODO: Remove once docker 1.5 is in usage (support for named Dockerfiles) -function docker_build { +function docker_build() { TAG=$1 DOCKERFILE=$2