Skip to content

Commit

Permalink
Add upgrade scripts for database files (#28)
Browse files Browse the repository at this point in the history
* Change: Add upgrade scripts for database files

Postgres changes the format of the database files on disk between major
versions. This means that in order to upgrade a postgres database, it is
not sufficient to just bump the container versions.

To help with this process, we now can use migration scripts that will
take care of updating the database version without the need of a manual
dump/restore.

* Update README with warning
  • Loading branch information
ailox authored Dec 4, 2024
1 parent 9886d85 commit eb34049
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 1 deletion.
4 changes: 4 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.dockerignore
LICENSE
README.md
tests/
35 changes: 34 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,2 +1,35 @@
ARG POSTGRES_VERSION=16
# This image was forked from the official PostgreSQL image, so it could be
# signed and relied on as an internal dependency.
# The image is modified to include an older version of PostgreSQL, which
# allows us to upgrade the database from the old version to the new one.
ARG POSTGRES_VERSION=17
FROM postgres:${POSTGRES_VERSION}

# we need to redeclare the ARG here, otherwise it will not
# be available in the section below the FROM statement.
ARG POSTGRES_VERSION=17
ARG POSTGRES_OLD_VERSION=16

ENV POSTGRES_VERSION=${POSTGRES_VERSION}
ENV POSTGRES_OLD_VERSION=${POSTGRES_OLD_VERSION}

# Enable and install old version of PostgreSQL.
RUN sed -i "s/\$/ ${POSTGRES_OLD_VERSION}/" /etc/apt/sources.list.d/pgdg.list
RUN set -eux; \
apt-get update; \
apt-get install -y --no-install-recommends \
postgresql-${POSTGRES_OLD_VERSION} \
; \
rm -rf /var/lib/apt/lists/*

# The old binaries will be in /usr/lib/postgresql/16/bin
ENV PGBINOLD /usr/lib/postgresql/${POSTGRES_OLD_VERSION}/bin

Check warning on line 26 in Dockerfile

View workflow job for this annotation

GitHub Actions / push-postgres

Legacy key/value format with whitespace separator should not be used

LegacyKeyValueFormat: "ENV key=value" should be used instead of legacy "ENV key value" format More info: https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/

Check warning on line 26 in Dockerfile

View workflow job for this annotation

GitHub Actions / push-postgres

Legacy key/value format with whitespace separator should not be used

LegacyKeyValueFormat: "ENV key=value" should be used instead of legacy "ENV key value" format More info: https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/
ENV PGBINNEW /usr/lib/postgresql/${POSTGRES_VERSION}/bin

Check warning on line 27 in Dockerfile

View workflow job for this annotation

GitHub Actions / push-postgres

Legacy key/value format with whitespace separator should not be used

LegacyKeyValueFormat: "ENV key=value" should be used instead of legacy "ENV key value" format More info: https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/

Check warning on line 27 in Dockerfile

View workflow job for this annotation

GitHub Actions / push-postgres

Legacy key/value format with whitespace separator should not be used

LegacyKeyValueFormat: "ENV key=value" should be used instead of legacy "ENV key value" format More info: https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/

# we are usually using /var/lib/postgresql/data as the data directory
# so this is why we are using it for the 'old' version instead of the
# path that is customized for the version.
ENV PGDATAOLD /var/lib/postgresql/data

Check warning on line 32 in Dockerfile

View workflow job for this annotation

GitHub Actions / push-postgres

Legacy key/value format with whitespace separator should not be used

LegacyKeyValueFormat: "ENV key=value" should be used instead of legacy "ENV key value" format More info: https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/

Check warning on line 32 in Dockerfile

View workflow job for this annotation

GitHub Actions / push-postgres

Legacy key/value format with whitespace separator should not be used

LegacyKeyValueFormat: "ENV key=value" should be used instead of legacy "ENV key value" format More info: https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/
ENV PGDATANEW /var/lib/postgresql/${POSTGRES_VERSION}/data

Check warning on line 33 in Dockerfile

View workflow job for this annotation

GitHub Actions / push-postgres

Legacy key/value format with whitespace separator should not be used

LegacyKeyValueFormat: "ENV key=value" should be used instead of legacy "ENV key value" format More info: https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/

Check warning on line 33 in Dockerfile

View workflow job for this annotation

GitHub Actions / push-postgres

Legacy key/value format with whitespace separator should not be used

LegacyKeyValueFormat: "ENV key=value" should be used instead of legacy "ENV key value" format More info: https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/

COPY bin/upgradeversion.sh /usr/local/bin/upgradeversion
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,37 @@

Opensight-Postgres is utilized to provide our Opensight services with the appropriate PostgreSQL container versions.

It is equivalent to the [postgres](https://hub.docker.com/_/postgres) Docker
image. Additionally one previous major version of postgres is added to the
image, so that it can be used to upgrade mounted database files to the
current version.

## Upgrade process
The opensight-postgres image can be used to upgrade older database versions
to the current version used by this image.

If the database is already upgraded, this will be a no-op and the upgrade
process will automatically terminate with exit code 0.

This does not happen automatically however, since it represents a backwards
incompatible change, and has potential to fail. It is therefore recommended
to create backups before attempting a migration.

Usage:

```
docker run \
-it --rm \
--user 999:999 \
-v /path/to/database:/var/lib/postgres/data/ \
opensight-postgres upgradeversion inplace
```

The inplace argument is required to perform the upgrade in the same mounted
directory. This has the implication that the database will be corrupted if
the upgrade fails. Therefore it is recommended to create a backup before
attempting the upgrade.

## Maintainer

This project is maintained by [Greenbone AG](https://www.greenbone.net/).
Expand Down
60 changes: 60 additions & 0 deletions bin/upgradeversion.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/bin/bash

set -euo pipefail

# POSTGRES_VERSION, POSTGRES_OLD_VERSION
# PGDATAOLD, PGDATANEW, PGBINOLD, PGBINNEW
# are available from the Dockerfile

# If there is no initialized database, we do nothing
if [ ! -s "${PGDATAOLD}/PG_VERSION" ]; then
echo "No database found. Exiting."
exit 0
fi

# Check postgres version
echo -n "Current database version is "
cat ${PGDATAOLD}/PG_VERSION

# if the version is already current, we don't need to do anything
if [ "$(cat ${PGDATAOLD}/PG_VERSION)" == "${POSTGRES_VERSION}" ]; then
echo "No need to update. exiting."
exit 0
fi

# Abort if the old version is not supported
if [ "$(cat ${PGDATAOLD}/PG_VERSION)" != "${POSTGRES_OLD_VERSION}" ]; then
echo "Only PostgreSQL ${POSTGRES_OLD_VERSION} is supported for migration with this image."
exit 1
fi

# Initialize the new database, if it doesn't exist
if [ ! -s "${PGDATANEW}/PG_VERSION" ]; then
initdb -D ${PGDATANEW} -U "${POSTGRES_USER}"

# migrate auth settings as well
cp --target-directory=${PGDATANEW}/ ${PGDATAOLD}/pg_hba.conf
fi

# mitigate: 'you must have read and write access in the current directory'
cd /var/lib/postgresql

pg_upgrade \
-U "${POSTGRES_USER}" \
--old-bindir ${PGBINOLD} \
--new-bindir ${PGBINNEW} \
--old-datadir ${PGDATAOLD} \
--new-datadir ${PGDATANEW}

# Check postgres version
echo -n "New migrated database version is "
cat ${PGDATANEW}/PG_VERSION

# if first parameter is 'inplace', we copy the new postgres version to
# the old one. This is not an atomic operation, and therefore risky;
# If the copy operation is not successful, we will end up with a
# corrupted database.
if [ "${1:-}" == "inplace" ]; then
rm -rf ${PGDATAOLD}/*
mv --target-directory=${PGDATAOLD}/ ${PGDATANEW}/*
fi
41 changes: 41 additions & 0 deletions tests/test-upgrade.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/env bash
set -euo pipefail

# This script is used to test the upgradeversion script without spinning up the
# whole OpenSight stack. It creates a new (outdated) PostgreSQL database,
# attempts to upgrade it and checks if the version file was updated correctly.

DOCKER_IMAGE=packages.greenbone.net/opensight/opensight-postgres
PREVIOUS_VERSION=16.6 # released version to test the upgrade from

tempdir=$(mktemp -d -t opensight-postgres-test-XXXXX)

cleanup() {
sudo rm -rf "${tempdir}"
}
trap cleanup ERR

sudo chown -R 999:999 "${tempdir}"

# Start a PostgreSQL container with an outdated version
docker run -it --rm \
--user 999:999 \
-e POSTGRES_PASSWORD=password \
-e POSTGRES_DB=keycloak \
-e POSTGRES_USER=keycloak \
-v "${tempdir}":/var/lib/postgresql/data \
${DOCKER_IMAGE}:${PREVIOUS_VERSION}

# Upgrade the database with the test version
docker build -t ${DOCKER_IMAGE}:test .
docker run -it --rm \
--user 999:999 -e POSTGRES_PASSWORD=password \
-e POSTGRES_DB=keycloak \
-e POSTGRES_USER=keycloak \
-v "${tempdir}":/var/lib/postgresql/data \
${DOCKER_IMAGE}:test upgradeversion inplace

# did everything work as expected?
sudo cat ${tempdir}/PG_VERSION

cleanup

0 comments on commit eb34049

Please sign in to comment.