From 3cdef0afbed0ddb3943f6475c373123dadea5684 Mon Sep 17 00:00:00 2001 From: Martin Nagy Date: Wed, 18 Mar 2015 15:37:57 +0100 Subject: [PATCH] Add PosgreSQL 9.2 image --- 9.2/Dockerfile | 40 ++++++++++ 9.2/Dockerfile.rhel7 | 41 ++++++++++ 9.2/contrib/.bashrc | 2 + 9.2/run-postgresql.sh | 91 +++++++++++++++++++++++ 9.2/test/run | 169 ++++++++++++++++++++++++++++++++++++++++++ Makefile | 20 +++++ README.md | 63 ++++++++++++++++ hack/build.sh | 55 ++++++++++++++ 8 files changed, 481 insertions(+) create mode 100644 9.2/Dockerfile create mode 100644 9.2/Dockerfile.rhel7 create mode 100755 9.2/contrib/.bashrc create mode 100755 9.2/run-postgresql.sh create mode 100755 9.2/test/run create mode 100644 Makefile create mode 100755 hack/build.sh diff --git a/9.2/Dockerfile b/9.2/Dockerfile new file mode 100644 index 00000000..5bac1373 --- /dev/null +++ b/9.2/Dockerfile @@ -0,0 +1,40 @@ +FROM centos:centos7 +MAINTAINER Martin Nagy + +# PostgreSQL image for OpenShift. +# Volumes: +# * /var/lib/psql/data - Database cluster for PostgreSQL +# Environment: +# * $POSTGRESQL_USER - Database user name +# * $POSTGRESQL_PASSWORD - User's password +# * $POSTGRESQL_DATABASE - Name of the database to create +# * $POSTGRESQL_ADMIN_PASSWORD (Optional) - Password for the 'postgres' +# PostgreSQL administrative account + +# Image metadata +ENV POSTGRESQL_VERSION 9.2 +ENV IMAGE_DESCRIPTION PostgreSQL 9.2 +ENV IMAGE_TAGS postgresql,postgresql92 +ENV IMAGE_EXPOSE_SERVICES 5432:postgresql + +EXPOSE 5432 + +# This image must forever use UID 26 for postgres user so our volumes are +# safe in the future. This should *never* change, the last test is there +# to make sure of that. +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/postgresql92/epel-7-x86_64/download/rhscl-postgresql92-epel-7-x86_64.noarch.rpm && \ + yum -y --setopt=tsflags=nodocs install postgresql92 && \ + yum clean all && \ + mkdir -p /var/lib/pgsql/data && chown postgres.postgres /var/lib/pgsql/data && \ + test "$(id postgres)" = "uid=26(postgres) gid=26(postgres) groups=26(postgres)" + +COPY run-postgresql.sh /usr/local/bin/ +COPY contrib/.bashrc /var/lib/pgsql/ + +VOLUME ["/var/lib/pgsql/data"] + +USER postgres + +ENTRYPOINT ["run-postgresql.sh"] +CMD ["postgres"] diff --git a/9.2/Dockerfile.rhel7 b/9.2/Dockerfile.rhel7 new file mode 100644 index 00000000..fc8c8d46 --- /dev/null +++ b/9.2/Dockerfile.rhel7 @@ -0,0 +1,41 @@ +FROM rhel7 +MAINTAINER Martin Nagy + +# PostgreSQL image for OpenShift. +# Volumes: +# * /var/lib/psql/data - Database cluster for PostgreSQL +# Environment: +# * $POSTGRESQL_USER - Database user name +# * $POSTGRESQL_PASSWORD - User's password +# * $POSTGRESQL_DATABASE - Name of the database to create +# * $POSTGRESQL_ADMIN_PASSWORD (Optional) - Password for the 'postgres' +# PostgreSQL administrative account + +# Image metadata +ENV POSTGRESQL_VERSION 9.2 +ENV IMAGE_DESCRIPTION PostgreSQL 9.2 +ENV IMAGE_TAGS postgresql,postgresql92 +ENV IMAGE_EXPOSE_SERVICES 5432:postgresql + +EXPOSE 5432 + +# This image must forever use UID 26 for postgres user so our volumes are +# safe in the future. This should *never* change, the last test is there +# to make sure of that. +RUN yum install -y yum-utils && \ + yum-config-manager --enable rhel-server-rhscl-7-rpms && \ + yum-config-manager --enable rhel-7-server-optional-rpms && \ + yum install -y --setopt=tsflags=nodocs postgresql92 && \ + yum clean all && \ + mkdir -p /var/lib/pgsql/data && chown postgres.postgres /var/lib/pgsql/data && \ + test "$(id postgres)" = "uid=26(postgres) gid=26(postgres) groups=26(postgres)" + +COPY run-postgresql.sh /usr/local/bin/ +COPY contrib/.bashrc /var/lib/pgsql/ + +VOLUME ["/var/lib/pgsql/data"] + +USER postgres + +ENTRYPOINT ["run-postgresql.sh"] +CMD ["postgres"] diff --git a/9.2/contrib/.bashrc b/9.2/contrib/.bashrc new file mode 100755 index 00000000..bbe6a0d3 --- /dev/null +++ b/9.2/contrib/.bashrc @@ -0,0 +1,2 @@ +# This will make scl collection binaries work out of box. +source scl_source enable postgresql92 diff --git a/9.2/run-postgresql.sh b/9.2/run-postgresql.sh new file mode 100755 index 00000000..32571c11 --- /dev/null +++ b/9.2/run-postgresql.sh @@ -0,0 +1,91 @@ +#!/bin/bash + +# For SCL enablement +source $HOME/.bashrc + +set -eu + +# Data dir +export PGDATA=/var/lib/pgsql/data + +# Be paranoid and stricter than we should be. +psql_identifier_regex='^[a-zA-Z_][a-zA-Z0-9_]*$' +psql_password_regex='^[a-zA-Z0-9_~!@#$%^&*()-=<>,.?;:|]+$' + +function usage() { + if [ $# == 2 ]; then + echo "error: $1" + fi + echo "You must specify following environment variables:" + echo " \$POSTGRESQL_USER (regex: '$psql_identifier_regex')" + echo " \$POSTGRESQL_PASSWORD (regex: '$psql_password_regex')" + echo " \$POSTGRESQL_DATABASE (regex: '$psql_identifier_regex')" + echo "Optional:" + echo " \$POSTGRESQL_ADMIN_PASSWORD (regex: '$psql_password_regex')" + exit 1 +} + +function check_env_vars() { + [[ "$POSTGRESQL_USER" =~ $psql_identifier_regex ]] || usage + [ ${#POSTGRESQL_USER} -le 63 ] || usage "PostgreSQL username too long (maximum 63 characters)" + [[ "$POSTGRESQL_PASSWORD" =~ $psql_password_regex ]] || usage + [[ "$POSTGRESQL_DATABASE" =~ $psql_identifier_regex ]] || usage + [ ${#POSTGRESQL_DATABASE} -le 63 ] || usage "Database name too long (maximum 63 characters)" + if [ -v POSTGRESQL_ADMIN_PASSWORD ]; then + [[ "$POSTGRESQL_ADMIN_PASSWORD" =~ $psql_password_regex ]] || usage + fi +} + +# Make sure env variables don't propagate to PostgreSQL process. +function unset_env_vars() { + unset POSTGRESQL_USER + unset POSTGRESQL_PASSWORD + unset POSTGRESQL_DATABASE + unset POSTGRESQL_ADMIN_PASSWORD +} + +if [ "$1" = "postgres" -a ! -f "$PGDATA/postgresql.conf" ]; then + + check_env_vars + + # Initialize the database cluster with utf8 support enabled by default. + # This might affect performance, see: + # http://www.postgresql.org/docs/9.2/static/locale.html + LANG=${LANG:-en_US.utf8} initdb + + # PostgreSQL configuration. + cat >> "$PGDATA/postgresql.conf" <<-EOF + + # + # Custom OpenShift configuration starting at this point. + # + + # Listen on all interfaces. + listen_addresses = '*' + EOF + + # Access control configuration. + cat >> "$PGDATA/pg_hba.conf" <<-EOF + + # + # Custom OpenShift configuration starting at this point. + # + + # Allow connections from all hosts. + host all all all md5 + EOF + + pg_ctl -w start + createuser "$POSTGRESQL_USER" + createdb --owner="$POSTGRESQL_USER" "$POSTGRESQL_DATABASE" + psql --command "ALTER USER \"${POSTGRESQL_USER}\" WITH ENCRYPTED PASSWORD '${POSTGRESQL_PASSWORD}';" + + if [ -v POSTGRESQL_ADMIN_PASSWORD ]; then + psql --command "ALTER USER \"postgres\" WITH ENCRYPTED PASSWORD '${POSTGRESQL_ADMIN_PASSWORD}';" + fi + + pg_ctl stop +fi + +unset_env_vars +exec "$@" diff --git a/9.2/test/run b/9.2/test/run new file mode 100755 index 00000000..0636dfa7 --- /dev/null +++ b/9.2/test/run @@ -0,0 +1,169 @@ +#!/bin/bash +# +# Test the PostgreSQL image. +# +# IMAGE_NAME specifies the name of the candidate image used for testing. +# The image has to be available before this script is executed. +# + +set -exo nounset +shopt -s nullglob + +IMAGE_NAME=${IMAGE_NAME-openshift/postgresql-92-centos7-candidate} + +CIDFILE_DIR=$(mktemp --suffix=postgresql_test_cidfiles -d) + +function cleanup() { + for cidfile in $CIDFILE_DIR/* ; do + CONTAINER=$(cat $cidfile) + + echo "Stopping and removing container $CONTAINER..." + docker stop $CONTAINER + exit_status=$(docker inspect -f '{{.State.ExitCode}}' $CONTAINER) + if [ "$exit_status" != "0" ]; then + echo "Dumping logs for $CONTAINER" + docker logs $CONTAINER + fi + docker rm $CONTAINER + rm $cidfile + echo "Done." + done + rmdir $CIDFILE_DIR +} +trap cleanup EXIT + +function get_cid() { + local id="$1" ; shift || return 1 + echo $(cat "$CIDFILE_DIR/$id") +} + +function get_container_ip() { + local id="$1" ; shift + docker inspect --format='{{.NetworkSettings.IPAddress}}' $(get_cid "$id") +} + +function postgresql_cmd() { + docker run --rm -e PGPASSWORD="${PASS}" $IMAGE_NAME psql postgresql://$USER@$CONTAINER_IP:5432/db "$@" +} + +function test_connection() { + local name=$1 ; shift + ip=$(get_container_ip $name) + echo " Testing PostgreSQL connection to $ip..." + local max_attempts=20 + local sleep_time=2 + for i in $(seq $max_attempts); do + echo " Trying to connect..." + set +e + postgresql_cmd <<< "SELECT 1;" + status=$? + set -e + if [ $status -eq 0 ]; then + echo " Success!" + return 0 + fi + sleep $sleep_time + done + return 1 +} + +function test_postgresql() { + echo " Testing PostgreSQL" + postgresql_cmd <<< "CREATE TABLE tbl (col1 VARCHAR(20), col2 VARCHAR(20));" + postgresql_cmd <<< "INSERT INTO tbl VALUES ('foo1', 'bar1');" + postgresql_cmd <<< "INSERT INTO tbl VALUES ('foo2', 'bar2');" + postgresql_cmd <<< "INSERT INTO tbl VALUES ('foo3', 'bar3');" + postgresql_cmd <<< "SELECT * FROM tbl;" + postgresql_cmd <<< "DROP TABLE tbl;" + echo " Success!" +} + +function create_container() { + local name=$1 ; shift + cidfile="$CIDFILE_DIR/$name" + # create container with a cidfile in a directory for cleanup + docker run --cidfile $cidfile -d "$@" $IMAGE_NAME + echo "Created container $(cat $cidfile)" +} + +function assert_login_access() { + local USER=$1 ; shift + local PASS=$1 ; shift + local success=$1 ; shift + + if $success; then + postgresql_cmd <<< "SELECT 1;" && + echo " $USER($PASS) access granted as expected" + else + postgresql_cmd <<< "SELECT 1;" || + echo " $USER($PASS) access denied as expected" + fi +} + +# 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, + # postgresql 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 POSTGRESQL_USER=user -e POSTGRESQL_PASSWORD=pass "$@" + assert_container_creation_fails -e POSTGRESQL_USER=user -e POSTGRESQL_DATABASE=db "$@" + assert_container_creation_fails -e POSTGRESQL_PASSWORD=pass -e POSTGRESQL_DATABASE=db "$@" +} + +function run_container_creation_tests() { + echo " Testing image entrypoint usage" + try_image_invalid_combinations + try_image_invalid_combinations -e POSTGRESQL_ADMIN_PASSWORD=admin_pass + + VERY_LONG_IDENTIFIER="very_long_identifier_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + assert_container_creation_fails -e POSTGRESQL_USER=0invalid -e POSTGRESQL_PASSWORD=pass -e POSTGRESQL_DATABASE=db -e POSTGRESQL_ADMIN_PASSWORD=admin_pass + assert_container_creation_fails -e POSTGRESQL_USER=$VERY_LONG_IDENTIFIER -e POSTGRESQL_PASSWORD=pass -e POSTGRESQL_DATABASE=db -e POSTGRESQL_ADMIN_PASSWORD=admin_pass + assert_container_creation_fails -e POSTGRESQL_USER=user -e POSTGRESQL_PASSWORD="\"" -e POSTGRESQL_DATABASE=db -e POSTGRESQL_ADMIN_PASSWORD=admin_pass + assert_container_creation_fails -e POSTGRESQL_USER=user -e POSTGRESQL_PASSWORD=pass -e POSTGRESQL_DATABASE=9invalid -e POSTGRESQL_ADMIN_PASSWORD=admin_pass + assert_container_creation_fails -e POSTGRESQL_USER=user -e POSTGRESQL_PASSWORD=pass -e POSTGRESQL_DATABASE=$VERY_LONG_IDENTIFIER -e POSTGRESQL_ADMIN_PASSWORD=admin_pass + assert_container_creation_fails -e POSTGRESQL_USER=user -e POSTGRESQL_PASSWORD=pass -e POSTGRESQL_DATABASE=db -e POSTGRESQL_ADMIN_PASSWORD="\"" + echo " Success!" +} + +function run_tests() { + local name=$1 ; shift + envs="-e POSTGRESQL_USER=$USER -e POSTGRESQL_PASSWORD=$PASS -e POSTGRESQL_DATABASE=db" + if [ -v ADMIN_PASS ]; then + envs="$envs -e POSTGRESQL_ADMIN_PASSWORD=$ADMIN_PASS" + fi + create_container $name $envs + CONTAINER_IP=$(get_container_ip $name) + test_connection $name + echo " Testing login accesses" + assert_login_access $USER $PASS true + assert_login_access $USER "${PASS}_foo" false + if [ -v ADMIN_PASS ]; then + assert_login_access postgres $ADMIN_PASS true + assert_login_access postgres "${ADMIN_PASS}_foo" false + else + assert_login_access postgres "foo" false + assert_login_access postgres "" false + fi + echo " Success!" + test_postgresql $name +} + +# Tests. + +run_container_creation_tests +USER=user PASS=pass run_tests no_admin +USER=user1 PASS=pass1 ADMIN_PASS=r00t run_tests admin diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..2545d06f --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ + +ifeq ($(TARGET),rhel7) + OS := rhel7 +else + OS := centos7 +endif + +ifeq ($(VERSION), 9.2) + VERSION := 9.2 +else + VERSION := +endif + +.PHONY: build +build: + hack/build.sh $(OS) $(VERSION) + +.PHONY: test +test: + TEST_MODE=true hack/build.sh $(OS) $(VERSION) diff --git a/README.md b/README.md index e69de29b..af968716 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,63 @@ +# OpenShift PostgreSQL image + +This repository contains Dockerfiles for PostgreSQL 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. + +```console +git clone https://github.com/openshift/postgresql.git +cd postgresql +make build TARGET=rhel7 +``` + +* **CentOS7 based image** + +```console +git clone https://github.com/openshift/postgresql.git +cd postgresql +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. + +| Variable name | Description | +| :--------------------------- | ---------------------------------------------- | +| `POSTGRESQL_USER` | User name for PostgreSQL account to be created | +| `POSTGRESQL_PASSWORD` | Password for the user account | +| `POSTGRESQL_DATABASE` | Database name | +| `POSTGRESQL_ADMIN_PASSWORD` | Password for the `postgres` admin account (optional) | + +You can also set following mount points by passing `-v /host:/container` flag to docker. + +| Volume mount point | Description | +| :----------------------- | ------------------------------------- | +| `/var/lib/pgsql/data` | PostgreSQL database cluster directory | + +## Usage + +We will assume that you are using the `openshift/postgresql-92-centos7` image. Supposing that you want to set only mandatory environment variables and not store the database in a host directory, you need to execute the following command: + +```console +docker run -d --name postgresql_database -e POSTGRESQL_USER=user -e POSTGRESQL_PASSWORD=pass -e POSTGRESQL_DATABASE=db -p 5432:5432 openshift/postgresql-92-centos7 +``` + +This will create a container named `postgresql_database` running PostgreSQL with database `db` and user with credentials `user:pass`. Port 5432 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/pgsql/data` argument. This is going to be the PostgreSQL database cluster directory. + +If the database cluster directory is not initialized, the entrypoint script will first run [`initdb`](http://www.postgresql.org/docs/9.2/static/app-initdb.html) and setup necessary database users and passwords. After the database is initialized, or if it was already present, [`postgres`](http://www.postgresql.org/docs/9.2/static/app-postgres.html) is executed and will run as PID 1. You can stop the detached container by running `docker stop postgresql_database`. + +### PostgreSQL admin account +The admin account `postgres` has no password set by default, only allowing local connections. You can set it by setting `POSTGRESQL_ADMIN_PASSWORD` environment variable when initializing your container. This will allow you to login to the postgres account remotely. Local connections will still not require password. + +## Software Collections +We use [Software Collections](https://www.softwarecollections.org/) to install and launch PostgreSQL. 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 PostgreSQL 9.2, the collection name will be "postgresql92": + +```console +docker exec -ti postgresql_database scl enable postgresql92 psql +``` diff --git a/hack/build.sh b/hack/build.sh new file mode 100755 index 00000000..3af4e1e8 --- /dev/null +++ b/hack/build.sh @@ -0,0 +1,55 @@ +#!/bin/bash -e +# $1 - Specifies distribution - RHEL7/CentOS7 +# $2 - Specifies PostgreSQL version - 9.2 +# TEST_MODE - If set, build a candidate image and test it + +# Array of all versions of PostgreSQL +declare -a VERSIONS=(9.2) + +OS=$1 +VERSION=$2 + +# TODO: Remove once docker 1.5 is in usage (support for named Dockerfiles) +function docker_build() { + TAG=$1 + DOCKERFILE=$2 + + if [ -n "$DOCKERFILE" -a "$DOCKERFILE" != "Dockerfile" ]; then + # Swap Dockerfiles and setup a trap restoring them + mv Dockerfile Dockerfile.centos7 + mv "${DOCKERFILE}" Dockerfile + trap "mv Dockerfile ${DOCKERFILE} && mv Dockerfile.centos7 Dockerfile" ERR RETURN + fi + + docker build -t ${TAG} . && trap - ERR +} + +if [ -z ${VERSION} ]; then + # Build all versions + dirs=${VERSIONS} +else + # Build only specified version on PostgreSQL + dirs=${VERSION} +fi + +for dir in ${dirs}; do + IMAGE_NAME=openshift/postgresql-${dir//./}-${OS} + if [ -v TEST_MODE ]; then + IMAGE_NAME="${IMAGE_NAME}-candidate" + fi + echo ">>>> Building ${IMAGE_NAME}" + + pushd ${dir} > /dev/null + + if [ "$OS" == "rhel7" ]; then + docker_build ${IMAGE_NAME} Dockerfile.rhel7 + else + docker_build ${IMAGE_NAME} + fi + + if [ -v TEST_MODE ]; then + IMAGE_NAME=${IMAGE_NAME} test/run + fi + + popd > /dev/null +done