diff options
Diffstat (limited to '')
-rwxr-xr-x | pg_virtualenv | 280 | ||||
-rw-r--r-- | pg_virtualenv.pod | 122 |
2 files changed, 402 insertions, 0 deletions
diff --git a/pg_virtualenv b/pg_virtualenv new file mode 100755 index 0000000..f4cfe03 --- /dev/null +++ b/pg_virtualenv @@ -0,0 +1,280 @@ +#!/bin/bash + +# Create a throw-away PostgreSQL environment for running regression tests. +# This does not interfere with existing clusters. +# +# (C) 2005-2012 Martin Pitt <mpitt@debian.org> +# (C) 2012-2020 Christoph Berg <myon@debian.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +set -e # no -u here as that breaks PGCONF_OPTS[@] + +# wrap ourselves in newpid if requested +if [ "$PG_VIRTUALENV_NEWPID" ]; then + unset PG_VIRTUALENV_NEWPID + exec newpid $0 "$@" +fi + +# wrap ourselves in unshare if requested +if [ "$PG_VIRTUALENV_UNSHARE" ]; then + export _PG_VIRTUALENV_UNSHARE="$PG_VIRTUALENV_UNSHARE" + unset PG_VIRTUALENV_UNSHARE + exec unshare $_PG_VIRTUALENV_UNSHARE -- $0 "$@" +fi +if [ "$_PG_VIRTUALENV_UNSHARE" ]; then + unset _PG_VIRTUALENV_UNSHARE + # start localhost interface + if [ -x /bin/ip ]; then + ip link set dev lo up || true + else + ifconfig lo up || true + fi +fi + +disable_fakeroot () +{ + case ${LD_PRELOAD:-} in + *fakeroot*) LD_PRELOAD=$(echo "$LD_PRELOAD" | sed -e 's/[^ ]*fakeroot[^ ]*//g') ;; + esac +} + +help () +{ + echo "pg_virtualenv: Create throw-away PostgreSQL environment for regression tests" + echo "Syntax: $0 [options] [command]" + echo " -a use all installed server versions" + echo " -v 'version ...' list of PostgreSQL versions to run [default: latest]" + echo " -c 'options' extra options to pass to pg_createcluster" + echo " -i 'initdb opts' extra initdb options to pass to pg_createcluster" + echo " -o 'guc=value' postgresql.conf options to pass to pg_createcluster" + echo " -p 'package' set options to find extension files in debian/package/" + echo " -s open a shell when command fails" + echo " -t use a temporary cluster directory even as root" + exit ${1:-0} +} + +# option parsing +PGBINROOT="/usr/lib/postgresql/" +#redhat# PGBINROOT="/usr/pgsql-" +PG_VERSIONS="" +PGCONF_OPTS=() +while getopts "ac:i:ho:p:stv:" opt ; do + case $opt in + a) for d in $PGBINROOT*/bin/pg_ctl; do + # prepend version so latest ends up first (i.e. on port 5432) + dir=${d%%/bin/pg_ctl} + PG_VERSIONS="${dir#$PGBINROOT} ${PG_VERSIONS:-}" + done ;; + c) CREATE_OPTS="$OPTARG" ;; + i) INITDB_OPTS="$OPTARG" ;; + h) help ;; + o) PGCONF_OPTS+=("--pgoption" "$OPTARG") ;; + p) PACKAGE="$OPTARG" ;; + s) run_shell=1 ;; + t) NONROOT=1 ;; + v) PG_VERSIONS="$OPTARG" ;; + *) help 1 ;; + esac +done +if [ -z "$PG_VERSIONS" ]; then + # use latest version + d=$(ls -v $PGBINROOT*/bin/pg_ctl 2> /dev/null | tail -1) + if [ -z "$d" ]; then + echo "Could not determine PostgreSQL version, are any PostgreSQL server packages installed?" >&2 + exit 2 + fi + dir=${d%%/bin/pg_ctl} + PG_VERSIONS="${dir#$PGBINROOT}" +fi +# shift away args +shift $(($OPTIND - 1)) +# if no command is given, open a shell +[ "${1:-}" ] || set -- ${SHELL:-/bin/sh} + +# generate a password +if [ -x /usr/bin/pwgen ]; then + export PGPASSWORD=$(pwgen 20 1) +else + export PGPASSWORD=$(dd if=/dev/urandom bs=1k count=1 2>/dev/null | md5sum - | awk '{ print $1 }') +fi + +# we are not root +if [ "$(id -u)" != 0 ]; then + NONROOT=1 +fi +# we aren't really root in fakeroot (but leave it enabled for commands spawned) +case ${LD_PRELOAD:-} in + *fakeroot*) NONROOT=1 ;; +esac + +# non-root operation: create a temp dir where we store everything +if [ "${NONROOT:-}" ]; then + WORKDIR=$(mktemp -d -t pg_virtualenv.XXXXXX) + if [ $(id -u) = 0 ]; then + chown postgres:postgres $WORKDIR + umask 022 + fi + export PG_CLUSTER_CONF_ROOT="$WORKDIR/postgresql" + export PGUSER="${USER:-${LOGNAME:-$(id -un)}}" + [ "$PGUSER" = "root" ] && PGUSER="postgres" + PGSYSCONFDIR="$WORKDIR/postgresql-common" # no export yet so pg_createcluster uses the original createcluster.conf + mkdir "$PGSYSCONFDIR" "$WORKDIR/log" + PWFILE="$PGSYSCONFDIR/pwfile" + LOGDIR="$WORKDIR/log" + echo "$PGPASSWORD" > "$PWFILE" + + cleanup () { + set +e + disable_fakeroot + for v in $PG_VERSIONS; do + # don't drop existing clusters named "regress" + [ -f $PG_CLUSTER_CONF_ROOT/$v/regress/.by_pg_virtualenv ] || continue + echo "Dropping cluster $v/regress ..." + pg_ctlcluster --mode immediate $v regress stop + pg_dropcluster $v regress + done + rm -rf $WORKDIR + } + trap cleanup 0 HUP INT QUIT ILL ABRT PIPE TERM + +# root: keep everything in the standard locations +else + for v in $PG_VERSIONS; do + if [ -d /etc/postgresql/$v/regress ]; then + echo "Cluster $v/regress exists, refusing to overwrite" >&2 + exit 2 + fi + done + + : ${PGSYSCONFDIR:=/etc/postgresql-common} + pg_service="$PGSYSCONFDIR/pg_service.conf" + + export PGUSER="postgres" + PWFILE=$(mktemp -t pgpassword.XXXXXX) + echo "$PGPASSWORD" > "$PWFILE" # write password before chowning the file + chown postgres:postgres "$PWFILE" + + cleanup () { + set +e + rm -f $PWFILE $pg_service + if [ -f $pg_service.pg_virtualenv-save.$$ ]; then + mv -f $pg_service.pg_virtualenv-save.$$ $pg_service + fi + for v in $PG_VERSIONS; do + # don't drop existing clusters named "regress" + [ -f /etc/postgresql/$v/regress/.by_pg_virtualenv ] || continue + echo "Dropping cluster $v/regress ..." + rm -f /etc/postgresql/$v/regress/.by_pg_virtualenv + pg_ctlcluster --mode immediate $v regress stop + pg_dropcluster $v regress + done + } + trap cleanup 0 HUP INT QUIT ILL ABRT PIPE TERM + + if [ -f $pg_service ]; then + mv --no-clobber $pg_service $pg_service.pg_virtualenv-save.$$ + fi +fi + +# create postgres environments +for v in $PG_VERSIONS; do + # create temporary cluster + if [ "${PACKAGE:-}" ]; then + if ! grep -q 'extension_destdir' /usr/share/postgresql/$v/postgresql.conf.sample; then + echo "$0: This PostgreSQL $v installation does not support 'extension_destdir', skipping this version" + [ "$PG_VERSIONS" = "$v" ] && exit 0 # only one version requested + continue + fi + PKGARGS="--pgoption extension_destdir=$PWD/debian/$PACKAGE --pgoption dynamic_library_path=$PWD/debian/$PACKAGE/usr/lib/postgresql/$v/lib:/usr/lib/postgresql/$v/lib" + fi + # we chdir to / so programs don't throw "could not change directory to ..." + ( + cd / + case $v in + 8*|9.0|9.1|9.2) : ;; + *) NOSYNC="--nosync" ;; + esac + if [ "${NONROOT:-}" ]; then + # stats_temp_directory in /var/run/postgresql needs root (or postgres), disable it + case $v in + 8.4|9.*|1[01234]) PGCONF_OPTS+=("--pgoption" "stats_temp_directory=") ;; + esac + fi + # disable fakeroot here because we really can't run as root + # (and still switch to postgres when using fakeroot as root user) + disable_fakeroot + pg_createcluster --quiet \ + ${PGPORT:+-p "$PGPORT"} \ + ${NONROOT:+-d "$WORKDIR/data/$v/regress"} \ + ${NONROOT:+-l "$WORKDIR/log/postgresql-$v-regress.log"} \ + ${CREATE_OPTS:-} --pgoption fsync=off ${PKGARGS:-} "${PGCONF_OPTS[@]}" \ + $v regress -- \ + --username="$PGUSER" --pwfile="$PWFILE" $NOSYNC ${INITDB_OPTS:-} + # in fakeroot, the username will likely default to "postgres" otherwise + echo "This is a temporary throw-away cluster" > ${PG_CLUSTER_CONF_ROOT:-/etc/postgresql}/$v/regress/.by_pg_virtualenv + # start cluster with coredumps enabled + [ $v != 8.2 ] && ENABLE_COREDUMPS="-c" + pg_ctlcluster $v regress start -- ${ENABLE_COREDUMPS:-} + ) + port=$(pg_conftool -s $v regress show port) + + # record cluster information in service file + cat >> $PGSYSCONFDIR/pg_service.conf <<EOF +[$v] +host=localhost +port=$port +dbname=postgres +user=$PGUSER +password=$PGPASSWORD + +EOF +done + +export PGSYSCONFDIR +export PGHOST="localhost" +export PGDATABASE="postgres" +unset PGHOSTADDR PGPORT PGSERVICE # unset variables that might interfere +case $PG_VERSIONS in + *\ *) ;; # multiple versions: do not set PGPORT because that breaks --cluster + *) + export PGPORT="$port" + export PG_CONFIG="$PGBINROOT$PG_VERSIONS/bin/pg_config" + export PGVERSION="$PG_VERSIONS" + ;; +esac + +# run program +"$@" || EXIT="$?" +if [ ${EXIT:-0} -gt 0 ]; then + for log in ${LOGDIR:-/var/log/postgresql}/*.log; do + echo "*** $log (last 100 lines) ***" + tail -100 $log + done + + if command -v gdb > /dev/null; then + for v in $PG_VERSIONS; do + PGDATA=$(pg_conftool -s $v regress show data_directory) + for core in $PGDATA/core*; do + [ -f "$core" ] || continue + echo "*** $core ***" + gdb -batch -ex "bt full" /usr/lib/postgresql/$v/bin/postgres "$core" + done + done + fi + + if [ "${run_shell:-}" ]; then + echo "pg_virtualenv: command exited with status $EXIT, dropping you into a shell" + ${SHELL:-/bin/sh} + fi +fi + +exit ${EXIT:-0} diff --git a/pg_virtualenv.pod b/pg_virtualenv.pod new file mode 100644 index 0000000..e1e05f0 --- /dev/null +++ b/pg_virtualenv.pod @@ -0,0 +1,122 @@ +=head1 NAME + +pg_virtualenv - Create a throw-away PostgreSQL environment for running regression tests + +=head1 SYNOPSIS + +B<pg_virtualenv> [I<OPTIONS>] [B<-v> 'I<version ...>'] [I<command>] + +=head1 DESCRIPTION + +B<pg_virtualenv> creates a virtual PostgreSQL server environment, and sets +environment variables such that I<command> can access the PostgreSQL database +server(s). The servers are destroyed when I<command> exits. + +The environment variables B<PGHOST>, B<PGDATABASE>, B<PGUSER>, and +B<PGPASSWORD> will be set. Per default, a single new cluster is created, +using the newest PostgreSQL server version installed. The cluster will use the +first available port number starting from B<5432>, and B<PGPORT> will be set. +B<PGVERSION> is set the the PostgreSQL major version number. + +When clusters for more than one versions are created, they will differ in the +port number used, and B<PGPORT> and B<PGVERSION> are not set. The clusters are +named I<version>/regress. To access a cluster, set +B<PGCLUSTER=>I<version>B</regress>. For ease of access, the clusters are also +registered in F</etc/postgresql-common/pg_service.conf>, with the version +number as cluster name. Clusters can be accessed by passing the connection +string "B<service=>I<version>", e.g. B<psql service=9.2>. + +When invoked as root, the clusters are created in F</etc/postgresql/> as usual; +for other users, B<PG_CLUSTER_CONF_ROOT> and B<PGSYSCONFDIR> are +set to a temporary directory where all files belonging to the clusters are +created. + +If I<command> fails, the tail of the PostgreSQL server log is shown. +Additionally, if B<gdb> is available, the backtrace from any PostgreSQL +coredump is show. + +=head1 OPTIONS + +=over 4 + +=item B<-a> + +Use all PostgreSQL server versions installed. + +=item B<-v> I<version ...> + +Use these versions (space-separated list). + +=item B<-c> I<pg_createcluster options> + +Extra options to pass to B<pg_createcluster>. + +=item B<-i> I<initdb options> + +Extra initdb options to pass to B<pg_createcluster>. + +=item B<-o> I<guc>B<=>I<value> + +Configuration option to set in the C<postgresql.conf> file, passed to +B<pg_createcluster>. + +=item B<-p> I<package> + +Set B<extension_destdir> and B<dynamic_library_path> in cluster to enable +loading and testing extensions at build-time from B<debian/>I<package>B</>. + +This is a Debian-specific PostgreSQL patch. + +=item B<-s> + +Launch a shell inside the virtual environment when I<command> fails. + +=item B<-t> + +Install clusters in a temporary directory, even when running as root. + +=item B<-h> + +Show program help. + +=back + +=head1 EXAMPLE + + # pg_virtualenv make check + +=head1 NOTES + +When run with fakeroot(1), B<pg_virtualenv> will fall back to the non-root mode +of operation. Running "fakeroot pg_virtualenv" as root will fail, though. + +=head1 ENVIRONMENT + +=over 4 + +=item B<PG_VIRTUALENV_NEWPID>=yes + +When non-empty, B<pg_virtualenv> will re-exec itself using newpid(1). + +=item B<PG_VIRTUALENV_UNSHARE>=I<flags> + +When non-empty, B<pg_virtualenv> will re-exec itself using unshare(1) using +these flags. + +=item B<PGPORT>=I<n> + +When set, the value is used for the (single) cluster created. + +=back + +=head1 COMPATIBILITY + +B<PGVERSION> is set in postgresql-common (>= 219~). + +=head1 SEE ALSO + +initdb(1), pg_createcluster(1). + +=head1 AUTHOR + +Christoph Berg L<E<lt>myon@debian.orgE<gt>> |