GNU bug report logs - #56045
[PATCH] Back up and restore PostgreSQL databases with Shepherd

Please note: This is a static page, with minimal formatting, updated once a day.
Click here to see this page with the latest information and nicer formatting.

Package: guix-patches; Reported by: Marius Bakke <marius@HIDDEN>; Keywords: moreinfo patch; dated Fri, 17 Jun 2022 21:15:02 UTC; Maintainer for guix-patches is guix-patches@HIDDEN.
Added tag(s) moreinfo. Request was from Ludovic Courtès <ludo@HIDDEN> to control <at> debbugs.gnu.org. Full text available.

Message received at 56045 <at> debbugs.gnu.org:


Received: (at 56045) by debbugs.gnu.org; 22 Jun 2022 20:46:46 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Wed Jun 22 16:46:46 2022
Received: from localhost ([127.0.0.1]:36722 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1o47FO-0008V6-6z
	for submit <at> debbugs.gnu.org; Wed, 22 Jun 2022 16:46:46 -0400
Received: from eggs.gnu.org ([209.51.188.92]:35434)
 by debbugs.gnu.org with esmtp (Exim 4.84_2)
 (envelope-from <ludo@HIDDEN>) id 1o47FM-0008Uv-Vc
 for 56045 <at> debbugs.gnu.org; Wed, 22 Jun 2022 16:46:46 -0400
Received: from fencepost.gnu.org ([2001:470:142:3::e]:52088)
 by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.90_1) (envelope-from <ludo@HIDDEN>) id 1o47FH-0003yY-Jn
 for 56045 <at> debbugs.gnu.org; Wed, 22 Jun 2022 16:46:39 -0400
DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=gnu.org;
 s=fencepost-gnu-org; h=MIME-Version:In-Reply-To:Date:References:Subject:To:
 From; bh=Srl7FLUDCSbmI+hXLg2wC+gEuZjZhZi9YPX4NLJsFqY=; b=eUHAkEu/zGZdWTL3YNpe
 zd+BV3pPlqwm09RqfTvn8o3c5yMhzgbjcxiaMuDjBEL7wXZsfhAGHpH7cJmkpzwpcG0AMAS6rRKkH
 qDICzR7v+rFJH7Fx/3pE1CN+7KkffgOu2rPuyaEKJKZomYCXHQCtfDcIAUTeEtqEM3FvbqnNdcZyo
 9VHWPo+8nfZp6Tp95HC3hD5ewieh/H3vVmFUUMeEYdRbZubGBEUeEWI+unZPiJPv14XLN+LZGDwNX
 P+07KSEXAIvk8bE2r5uu2uOESrbDXR4hmJcjjwCL4FNGWWkK/0PVwinisuhdW+fhLkRdq48cOfWQe
 7zdTH7IzKM9HmQ==;
Received: from 91-160-117-201.subs.proxad.net ([91.160.117.201]:53889
 helo=ribbon)
 by fencepost.gnu.org with esmtpsa (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.90_1) (envelope-from <ludo@HIDDEN>)
 id 1o47FG-0004fj-F0; Wed, 22 Jun 2022 16:46:38 -0400
From: =?utf-8?Q?Ludovic_Court=C3=A8s?= <ludo@HIDDEN>
To: Marius Bakke <marius@HIDDEN>
Subject: Re: bug#56045: [PATCH] Back up and restore PostgreSQL databases
 with Shepherd
References: <87zgibuh5w.fsf@HIDDEN>
Date: Wed, 22 Jun 2022 22:46:36 +0200
In-Reply-To: <87zgibuh5w.fsf@HIDDEN> (Marius Bakke's message of "Fri, 17 Jun
 2022 23:14:03 +0200")
Message-ID: <87v8ss1l5f.fsf@HIDDEN>
User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/28.1 (gnu/linux)
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable
X-Spam-Score: -2.3 (--)
X-Debbugs-Envelope-To: 56045
Cc: 56045 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -3.3 (---)

Hello!

Marius Bakke <marius@HIDDEN> skribis:

> The attached patch adds backup and restore mechanisms to the PostgreSQL
> Shepherd service.  It looks like this (here with a db named 'mreg'):
>
> $ sudo herd backup postgres mreg
> $ sudo -u postgres psql -c 'drop database mreg' # whoops ...
> DROP DATABASE
> $ sudo herd list-backups postgres mreg
> mreg@2022-06-16_21-55-07
> mreg@2022-06-16_22-48-59
> $ sudo herd restore postgres mreg@2022-06-16_22-48-59
> $ sudo -u postgres psql mreg
> mreg=3D#
>
> Pretty cool, no?  :-)

Indeed!  :-)

> With this patch you can 'herd backup' each database, stop postgres,
> _delete_ /var/lib/postgresql/data, reconfigure with a newer version, and
> 'herd restore' them again -- but you'll lose any role passwords (and
> roles not declared by postgresql-role-service-type).
>
> Not sure what to about roles, maybe a backup-roles command?

No idea, we need input from PG practitioners!

> From edc8a2e5ae3c89b78fb837d4351f0ddfab8fe474 Mon Sep 17 00:00:00 2001
> From: Marius Bakke <marius@HIDDEN>
> Date: Thu, 16 Jun 2022 22:46:01 +0200
> Subject: [PATCH] services: Shepherd can backup and restore PostgreSQL
>  databases.
>
> * gnu/services/databases.scm (<postgresql-configuration>)[backup-director=
y]:
> New field.
> (postgresql-activation): Create it.
> (postgresql-backup-action, postgresql-list-backups-action,
> postgresql-restore-action): New variables.
> (postgresql-shepherd-service)[actions]: Register them.
> * gnu/tests/databases.scm (%postgresql-backup-directory): New variable.
> (run-postgresql-test): Trim unused module imports from existing tests.  A=
dd
> "insert test data", "backup database", "list backups", "drop database",
> "restore database", "update test data", "restore again", and "verify rest=
ore"
> tests.

Not being a database person, I=E2=80=99ll comment on the code:

>    (match-lambda
>      (($ <postgresql-configuration> postgresql port locale config-file
> -                                   log-directory data-directory
> +                                   log-directory data-directory backup-d=
irectory
>                                     extension-packages)

Time to use =E2=80=98match-record=E2=80=99!

> +(define (postgresql-backup-action postgresql backup-directory)

Please add a docstring (and on other top-level procedures).

> +   (procedure
> +    #~(lambda* (pid #:optional database #:rest rest)
> +        (use-modules (guix build utils)
> +                     (ice-9 match)
> +                     (srfi srfi-19))

Non-top-level =E2=80=98use-modules=E2=80=99 should be avoided; it=E2=80=99s=
 not really supposed
to work.  If you have these three modules in the =E2=80=98modules=E2=80=99 =
field of the
parent <shepherd-service> record, that=E2=80=99s enough (I know, it=E2=80=
=99s not pretty).

> +              ;; Fork so we can drop privileges.
> +              (match (primitive-fork)
> +                (0
> +                 ;; Exit with a non-zero status code if an exception is =
thrown.
> +                 (dynamic-wind
> +                   (const #t)
> +                   (lambda ()
> +                     (setgid (passwd:gid user))
> +                     (setuid (passwd:uid user))
> +                     (umask #o027)
> +                     (format (current-output-port)
> +                             "postgres: creating backup ~a.~%"
> +                             (basename file-name))
> +                     (mkdir-p (dirname file-name))
> +                     (let* ((result (apply system* pg_dump database
> +                                           "-f" file-name
> +                                           options))
> +                            (exit-value (status:exit-val result)))

Would it work to use =E2=80=98fork+exec-command=E2=80=99 to do all this?  I=
t=E2=80=99d be great
if we could avoid the boilerplate.

> +(define (postgresql-list-backups-action backup-directory)

Docstring.  :-)

> +              (match (primitive-fork)
> +                (0
> +                 (dynamic-wind
> +                   (const #t)
> +                   (lambda ()
> +                     (setgid (passwd:gid user))
> +                     (setuid (passwd:uid user))
> +                     (let* ((backup-file (string-append #$backup-directo=
ry
> +                                                        "/" file))
> +                            (database (match (string-split file #\@)
> +                                        ((name date) name)))
> +                            (create? (not (database-exists? database)))
> +                            (options (list "--clean" "--if-exists"
> +                                           (if create?
> +                                               "--create"
> +                                               "--single-transaction"))))
> +                       (format (current-output-port)
> +                               "postgres: restoring ~a.~%" file)
> +                       (let* ((result (apply system* pg_restore backup-f=
ile
> +                                             "-d" (if create? "postgres"=
 database)
> +                                             options))

Same here: =E2=80=98fork+exec-command=E2=80=99?

Overall I find it nice and convenient, but I wonder how far we should go
with our services.  After all, it=E2=80=99s just one way to make backups, t=
here
are probably other ways, so should we have this particular method
hardwired?

Thanks,
Ludo=E2=80=99.




Information forwarded to guix-patches@HIDDEN:
bug#56045; Package guix-patches. Full text available.

Message received at submit <at> debbugs.gnu.org:


Received: (at submit) by debbugs.gnu.org; 17 Jun 2022 21:14:29 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Fri Jun 17 17:14:29 2022
Received: from localhost ([127.0.0.1]:47111 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1o2JIO-0001Ig-Sv
	for submit <at> debbugs.gnu.org; Fri, 17 Jun 2022 17:14:29 -0400
Received: from lists.gnu.org ([209.51.188.17]:35798)
 by debbugs.gnu.org with esmtp (Exim 4.84_2)
 (envelope-from <marius@HIDDEN>) id 1o2JIL-0001IW-5n
 for submit <at> debbugs.gnu.org; Fri, 17 Jun 2022 17:14:23 -0400
Received: from eggs.gnu.org ([2001:470:142:3::10]:38888)
 by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.90_1) (envelope-from <marius@HIDDEN>) id 1o2JIL-000071-0X
 for guix-patches@HIDDEN; Fri, 17 Jun 2022 17:14:21 -0400
Received: from fencepost.gnu.org ([2001:470:142:3::e]:45324)
 by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.90_1) (envelope-from <marius@HIDDEN>) id 1o2JIK-0000JG-Og
 for guix-patches@HIDDEN; Fri, 17 Jun 2022 17:14:20 -0400
DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=gnu.org;
 s=fencepost-gnu-org; h=MIME-Version:Date:Subject:To:From:in-reply-to:
 references; bh=8x+ls4giJ0GMNhtyAPdvDiRvtzVUpGZn0iGcv9vPvh0=; b=CyQJens+16wgGv
 PdGErYGtDWR+uQeeijqDOR10l0J95YbCyyUuc3SicjmUdKBohTQWtYnyCNapXCpEUqrNT8DneBz4c
 LkKkRpaPGe3v8DKFgCripfimz3AIUvnaF4YaNhxdMdHETR3ZHU+BSN6PHhkJUZXTTZE/gOz+LHgCW
 gJN6roLrBbuVAUEyEsA+9n2OFgize0/ULCP0s5HCgHEZAQmzQySXltTzwe56ChwImvxTdk2s0H5OC
 fWw+EJpH18pHYkMh+bs3P38eJTAB6PatQ9B7mwSRWh0Oox8AtjTBJ6xAsSM7BQmZSiSKECSLAx7tW
 TjM18IL+SQ99uKUzZ4fw==;
Received: from [2001:4652:9717:0:52eb:71ff:fe49:3a13] (port=54830
 helo=localhost)
 by fencepost.gnu.org with esmtpsa (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.90_1) (envelope-from <marius@HIDDEN>) id 1o2JIK-0007a8-87
 for guix-patches@HIDDEN; Fri, 17 Jun 2022 17:14:20 -0400
From: Marius Bakke <marius@HIDDEN>
To: guix-patches@HIDDEN
Subject: [PATCH] Back up and restore PostgreSQL databases with Shepherd
Date: Fri, 17 Jun 2022 23:14:03 +0200
Message-ID: <87zgibuh5w.fsf@HIDDEN>
MIME-Version: 1.0
Content-Type: multipart/signed; boundary="==-=-=";
 micalg=pgp-sha512; protocol="application/pgp-signature"
X-Spam-Score: -0.7 (/)
X-Debbugs-Envelope-To: submit
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.7 (-)

--==-=-=
Content-Type: multipart/mixed; boundary="=-=-="

--=-=-=
Content-Type: text/plain

Hello Guix!

The attached patch adds backup and restore mechanisms to the PostgreSQL
Shepherd service.  It looks like this (here with a db named 'mreg'):

$ sudo herd backup postgres mreg
$ sudo -u postgres psql -c 'drop database mreg' # whoops ...
DROP DATABASE
$ sudo herd list-backups postgres mreg
mreg@2022-06-16_21-55-07
mreg@2022-06-16_22-48-59
$ sudo herd restore postgres mreg@2022-06-16_22-48-59
$ sudo -u postgres psql mreg
mreg=#

Pretty cool, no?  :-)

The restore command is "smart": if the database already exists, it
restores in a single transaction; otherwise, it will be created from
scratch (these scenarios require mutually exclusive options to
'pg_restore').

With this patch you can 'herd backup' each database, stop postgres,
_delete_ /var/lib/postgresql/data, reconfigure with a newer version, and
'herd restore' them again -- but you'll lose any role passwords (and
roles not declared by postgresql-role-service-type).

Not sure what to about roles, maybe a backup-roles command?

There is no Scheme API yet, but it would be nice to define per-database
settings (i.e. --jobs or --format) in the configuration.  And also a
scheduled backup service.  These tasks are up for grabs.  :-)

The quest here is to provide a smooth upgrade path for end users (and
eventually bump the old 'postgresql-10' service default).

Feedback and/or testing welcome!


--=-=-=
Content-Type: text/x-patch; charset=utf-8
Content-Disposition: attachment;
 filename=0001-services-Shepherd-can-backup-and-restore-PostgreSQL-.patch
Content-Transfer-Encoding: quoted-printable

From=20edc8a2e5ae3c89b78fb837d4351f0ddfab8fe474 Mon Sep 17 00:00:00 2001
From: Marius Bakke <marius@HIDDEN>
Date: Thu, 16 Jun 2022 22:46:01 +0200
Subject: [PATCH] services: Shepherd can backup and restore PostgreSQL
 databases.

* gnu/services/databases.scm (<postgresql-configuration>)[backup-directory]:
New field.
(postgresql-activation): Create it.
(postgresql-backup-action, postgresql-list-backups-action,
postgresql-restore-action): New variables.
(postgresql-shepherd-service)[actions]: Register them.
* gnu/tests/databases.scm (%postgresql-backup-directory): New variable.
(run-postgresql-test): Trim unused module imports from existing tests.  Add
"insert test data", "backup database", "list backups", "drop database",
"restore database", "update test data", "restore again", and "verify restor=
e"
tests.
=2D--
 gnu/services/databases.scm | 169 ++++++++++++++++++++++++++++++++++++-
 gnu/tests/databases.scm    | 117 ++++++++++++++++++++++++-
 2 files changed, 278 insertions(+), 8 deletions(-)

diff --git a/gnu/services/databases.scm b/gnu/services/databases.scm
index fb3cd3c478..e3e8cc724e 100644
=2D-- a/gnu/services/databases.scm
+++ b/gnu/services/databases.scm
@@ -6,7 +6,7 @@
 ;;; Copyright =C2=A9 2018 Cl=C3=A9ment Lassieur <clement@HIDDEN>
 ;;; Copyright =C2=A9 2018 Julien Lepiller <julien@HIDDEN>
 ;;; Copyright =C2=A9 2019 Robert Vollmert <rob@HIDDEN>
=2D;;; Copyright =C2=A9 2020 Marius Bakke <marius@HIDDEN>
+;;; Copyright =C2=A9 2020, 2022 Marius Bakke <marius@HIDDEN>
 ;;; Copyright =C2=A9 2021 David Larsson <david.larsson@HIDDEN>
 ;;;
 ;;; This file is part of GNU Guix.
@@ -176,6 +176,8 @@ (define-record-type* <postgresql-configuration>
                       (default "/var/log/postgresql"))
   (data-directory     postgresql-configuration-data-directory
                       (default "/var/lib/postgresql/data"))
+  (backup-directory   postgresql-configuration-backup-directory
+                      (default "/var/lib/postgresql/backup"))
   (extension-packages postgresql-configuration-extension-packages
                       (default '())))
=20
@@ -213,7 +215,7 @@ (define (final-postgresql postgresql extension-packages)
 (define postgresql-activation
   (match-lambda
     (($ <postgresql-configuration> postgresql port locale config-file
=2D                                   log-directory data-directory
+                                   log-directory data-directory backup-dir=
ectory
                                    extension-packages)
      #~(begin
          (use-modules (guix build utils)
@@ -245,6 +247,11 @@ (define postgresql-activation
              (mkdir-p #$log-directory)
              (chown #$log-directory (passwd:uid user) (passwd:gid user)))
=20
+           ;; Create the backup directory.
+           (when (string? #$backup-directory)
+             (mkdir-p #$backup-directory)
+             (chown #$backup-directory (passwd:uid user) (passwd:gid user)=
))
+
            ;; Drop privileges and init state directory in a new
            ;; process.  Wait for it to finish before proceeding.
            (match (primitive-fork)
@@ -265,10 +272,155 @@ (define postgresql-activation
                   (primitive-exit 1))))
              (pid (waitpid pid))))))))
=20
+(define (postgresql-backup-action postgresql backup-directory)
+  (shepherd-action
+   (name 'backup)
+   (documentation
+    "Back up a database on the running PostgreSQL server.")
+   (procedure
+    #~(lambda* (pid #:optional database #:rest rest)
+        (use-modules (guix build utils)
+                     (ice-9 match)
+                     (srfi srfi-19))
+        (if database
+            (let* ((user (getpwnam "postgres"))
+                   (pg_dump #$(file-append postgresql "/bin/pg_dump"))
+                   (options '("--create" "--clean" "--if-exists"
+                              "--format=3Dd"))
+                   (start-time (current-time))
+                   (date (time-utc->date start-time))
+                   (date-stamp (date->string date "~1_~H-~M-~S"))
+                   (file-name (string-append #$backup-directory "/"
+                                             database "@" date-stamp)))
+              ;; Fork so we can drop privileges.
+              (match (primitive-fork)
+                (0
+                 ;; Exit with a non-zero status code if an exception is th=
rown.
+                 (dynamic-wind
+                   (const #t)
+                   (lambda ()
+                     (setgid (passwd:gid user))
+                     (setuid (passwd:uid user))
+                     (umask #o027)
+                     (format (current-output-port)
+                             "postgres: creating backup ~a.~%"
+                             (basename file-name))
+                     (mkdir-p (dirname file-name))
+                     (let* ((result (apply system* pg_dump database
+                                           "-f" file-name
+                                           options))
+                            (exit-value (status:exit-val result)))
+                       (if (=3D 0 exit-value)
+                           (format (current-output-port)
+                                   "postgres: backup of ~a completed succe=
ssfully.~%"
+                                   database)
+                           (format (current-output-port)
+                                   "postgres: backup of ~a completed with =
errors.~%"
+                                   database))
+                       (primitive-exit exit-value)))
+                   (lambda ()
+                     (format (current-output-port)
+                             "postgres: backup of ~a failed.~%")
+                     (primitive-exit 1))))
+                (pid (waitpid pid))))
+            (begin
+              (format #t "usage: herd backup postgres DATABASE~%")
+              #f))))))
+
+(define (postgresql-list-backups-action backup-directory)
+  (shepherd-action
+   (name 'list-backups)
+   (documentation
+    "List available PostgreSQL backups.")
+   (procedure
+    #~(lambda* (pid #:optional database #:rest rest)
+        (use-modules (ice-9 ftw)
+                     (srfi srfi-26))
+        (if (file-exists? #$backup-directory)
+            (for-each (cut format #t "~a~%" <>)
+                      (scandir #$backup-directory
+                               (if database
+                                   (cut string-prefix? database <>)
+                                   (negate (cut member <> '("." ".."))))))
+            #f)))))
+
+(define (postgresql-restore-action postgresql backup-directory)
+  (shepherd-action
+   (name 'restore)
+   (documentation
+    "Restore a PostgreSQL backup.")
+   (procedure
+    #~(lambda* (pid #:optional file #:rest rest)
+        (use-modules (ice-9 match)
+                     (ice-9 popen)
+                     (ice-9 rdelim))
+
+        ;; The pg_restore arguments varies slightly if the database is
+        ;; missing vs already present, hence this procedure.
+        (define (database-exists? db)
+          (let* ((psql #$(file-append postgresql "/bin/psql"))
+                 (separator "%")
+                 (port (open-input-pipe (string-append psql " -lqtA"
+                                                       " -F " separator))))
+            (let loop ((line (read-line port)))
+              (cond
+               ((eof-object? line)
+                (close-port port)
+                #f)
+               ((string-prefix? (string-append db separator) line)
+                (close-port port)
+                #t)
+               (else (loop (read-line port)))))))
+
+        (let ((user (getpwnam "postgres"))
+              (pg_restore #$(file-append postgresql "/bin/pg_restore")))
+          (if (and (string? file)
+                   (file-exists? (string-append #$backup-directory "/" fil=
e)))
+              (match (primitive-fork)
+                (0
+                 (dynamic-wind
+                   (const #t)
+                   (lambda ()
+                     (setgid (passwd:gid user))
+                     (setuid (passwd:uid user))
+                     (let* ((backup-file (string-append #$backup-directory
+                                                        "/" file))
+                            (database (match (string-split file #\@)
+                                        ((name date) name)))
+                            (create? (not (database-exists? database)))
+                            (options (list "--clean" "--if-exists"
+                                           (if create?
+                                               "--create"
+                                               "--single-transaction"))))
+                       (format (current-output-port)
+                               "postgres: restoring ~a.~%" file)
+                       (let* ((result (apply system* pg_restore backup-file
+                                             "-d" (if create? "postgres" d=
atabase)
+                                             options))
+                              (exit-value (status:exit-val result)))
+                         (if (=3D 0 exit-value)
+                             (format (current-output-port)
+                                     "postgres: restore of ~a completed \
+successfully.~%"
+                                     database)
+                             (format (current-output-port)
+                                     "postgres: restore of ~a completed \
+with errors.~%"
+                                     database))
+                         (primitive-exit exit-value))))
+                   (lambda ()
+                     (format #t "postgres: could not restore ~a.~%" file)
+                     (primitive-exit 1))))
+                (pid (waitpid pid)))
+              (begin
+                (format #t "usage: herd restore postgres BACKUP~%")
+                (format #t "hint: see 'herd list-backups postgres'~%")
+                #f)))))))
+
 (define postgresql-shepherd-service
   (match-lambda
     (($ <postgresql-configuration> postgresql port locale config-file
=2D                                   log-directory data-directory
+                                   log-directory data-directory backup-dir=
ectory
                                    extension-packages)
      (let* ((pg_ctl-wrapper
              ;; Wrapper script that switches to the 'postgres' user before
@@ -309,8 +461,17 @@ (define postgresql-shepherd-service
               (provision '(postgres))
               (documentation "Run the PostgreSQL daemon.")
               (requirement '(user-processes loopback syslogd))
=2D              (modules `((ice-9 match)
+              (modules `((ice-9 ftw)
+                         (ice-9 match)
+                         (ice-9 popen)
+                         (ice-9 rdelim)
+                         (srfi srfi-19)
+                         (srfi srfi-26)
                          ,@%default-modules))
+              (actions (list
+                        (postgresql-backup-action postgresql backup-direct=
ory)
+                        (postgresql-list-backups-action backup-directory)
+                        (postgresql-restore-action postgresql backup-direc=
tory)))
               (start (action "start"))
               (stop (action "stop"))))))))
=20
diff --git a/gnu/tests/databases.scm b/gnu/tests/databases.scm
index 296d91d118..4210054d9e 100644
=2D-- a/gnu/tests/databases.scm
+++ b/gnu/tests/databases.scm
@@ -134,6 +134,9 @@ (define %test-memcached
 ;;; The PostgreSQL service.
 ;;;
=20
+(define %postgresql-backup-directory
+  "/var/lib/postgresql/backup")
+
 (define %postgresql-log-directory
   "/var/log/postgresql")
=20
@@ -195,8 +198,6 @@ (define marionette
           (test-assert "log-file"
             (marionette-eval
              '(begin
=2D                (use-modules (ice-9 ftw)
=2D                             (ice-9 match))
                 (current-output-port
                  (open-file "/dev/console" "w0"))
                 (let ((server-log-file
@@ -227,8 +228,7 @@ (define marionette
           (test-assert "database creation"
             (marionette-eval
              '(begin
=2D                (use-modules (gnu services herd)
=2D                             (ice-9 popen))
+                (use-modules (ice-9 popen))
                 (current-output-port
                  (open-file "/dev/console" "w0"))
                 (let* ((port (open-pipe*
@@ -241,6 +241,115 @@ (define marionette
                   (string-contains output "1")))
              marionette))
=20
+          (test-eq "insert test data"
+            0
+            (marionette-eval
+             '(begin
+                (current-output-port
+                 (open-file "/dev/console" "w0"))
+                (let ((result (system*
+                               #$(file-append postgresql "/bin/psql")
+                               "-tA" "-c" "CREATE TABLE test (name VARCHAR,
+                                                              status VARCH=
AR);
+INSERT INTO TEST VALUES ('backup', 'pending');"
+                               "root")))
+                       (status:exit-val result)))
+             marionette))
+
+          (test-assert "backup database"
+            (marionette-eval
+             '(with-shepherd-action 'postgres ('backup "root")
+                                    result
+                result)
+             marionette))
+
+          (test-assert "list backups"
+            (marionette-eval
+             '(with-shepherd-action 'postgres ('list-backups)
+                                    result
+                result)
+             marionette))
+
+          (test-eq "drop database"
+            0
+            (marionette-eval
+             '(begin
+                (current-output-port
+                 (open-file "/dev/console" "w0"))
+                (let ((result (system*
+                               #$(file-append postgresql "/bin/psql")
+                               "-tA" "-c" "DROP DATABASE root"
+                               "postgres")))
+                       (status:exit-val result)))
+             marionette))
+
+          (test-assert "restore database"
+            (let ((file-name (marionette-eval
+                              '(begin
+                                 (use-modules (ice-9 ftw)
+                                              (srfi srfi-26))
+                                 (car (scandir #$%postgresql-backup-direct=
ory
+                                               (negate (cut member <>
+                                                            '("." ".."))))=
))
+                              marionette)))
+              (marionette-eval
+               `(with-shepherd-action 'postgres ('restore ,file-name)
+                                      result
+                  result)
+               marionette)))
+
+          (test-equal "update test data"
+            "completed"
+            (marionette-eval
+             '(begin
+                (use-modules (ice-9 popen))
+                (current-output-port
+                 (open-file "/dev/console" "w0"))
+                (let* ((port (open-pipe*
+                              OPEN_READ
+                              #$(file-append postgresql "/bin/psql")
+                              "-tA" "-c" "
+UPDATE test SET status=3D'completed' WHERE name=3D'backup';
+SELECT status FROM test WHERE name=3D'backup';"
+                              "root"))
+                       (output (get-string-all port)))
+                  (close-pipe port)
+                  (string-trim-right output)))
+             marionette))
+
+          (test-assert "restore again"
+            (let ((file-name (marionette-eval
+                              '(begin
+                                 (use-modules (ice-9 ftw)
+                                              (srfi srfi-26))
+                                 (car (scandir #$%postgresql-backup-direct=
ory
+                                               (negate (cut member <>
+                                                            '("." ".."))))=
))
+                              marionette)))
+              (marionette-eval
+               `(with-shepherd-action 'postgres ('restore ,file-name)
+                                      result
+                  result)
+               marionette)))
+
+          (test-equal "verify restore"
+            "pending"
+            (marionette-eval
+             '(begin
+                (use-modules (ice-9 popen))
+                (current-output-port
+                 (open-file "/dev/console" "w0"))
+                (let* ((port (open-pipe*
+                              OPEN_READ
+                              #$(file-append postgresql "/bin/psql")
+                              "-tA" "-c" "
+SELECT status FROM test WHERE name=3D'backup'"
+                              "root"))
+                       (output (get-string-all port)))
+                  (close-pipe port)
+                  (string-trim-right output)))
+             marionette))
+
           (test-end))))
=20
   (gexp->derivation "postgresql-test" test))
=2D-=20
2.36.1


--=-=-=--

--==-=-=
Content-Type: application/pgp-signature; name="signature.asc"

-----BEGIN PGP SIGNATURE-----

iIUEARYKAC0WIQRNTknu3zbaMQ2ddzTocYulkRQQdwUCYqzumw8cbWFyaXVzQGdu
dS5vcmcACgkQ6HGLpZEUEHdqGQD/RhKGfvcFZYr4Eo8ico0Y2xt18IUymNgfGq7U
oJJsZJgBAM4/gLnUqpPq/NDoTSjmMAE50Ss6rbefVrtGXFX0DMkM
=qMDU
-----END PGP SIGNATURE-----
--==-=-=--




Acknowledgement sent to Marius Bakke <marius@HIDDEN>:
New bug report received and forwarded. Copy sent to guix-patches@HIDDEN. Full text available.
Report forwarded to guix-patches@HIDDEN:
bug#56045; Package guix-patches. Full text available.
Please note: This is a static page, with minimal formatting, updated once a day.
Click here to see this page with the latest information and nicer formatting.
Last modified: Thu, 4 Aug 2022 09:15:01 UTC

GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997 nCipher Corporation Ltd, 1994-97 Ian Jackson.