services: postgresql: Add postgresql-role-service-type.

* gnu/services/databases.scm (postgresql-role,
postgresql-role?, postgresql-role-name,
postgresql-role-permissions, postgresql-role-create-database?,
postgresql-role-configuration, postgresql-role-configuration?,
postgresql-role-configuration-host, postgresql-role-configuration-roles,
postgresql-role-service-type): New procedures.
* gnu/tests/databases.scm: Test it.
* doc/guix.texi: Document it.
This commit is contained in:
Mathieu Othacehe 2021-01-18 11:10:28 +01:00
parent 33687aa3d0
commit ec145a2ff9
No known key found for this signature in database
GPG key ID: 8354763531769CA6
3 changed files with 207 additions and 1 deletions

View file

@ -19455,6 +19455,68 @@ here}.
@end table
@end deftp
@deffn {Scheme Variable} postgresql-role-service-type
This service allows to create PostgreSQL roles and databases after
PostgreSQL service start. Here is an example of its use.
@lisp
(service postgresql-role-service-type
(postgresql-role-configuration
(roles
(list (postgresql-role
(name "test")
(create-database? #t))))))
@end lisp
This service can be extended with extra roles, as in this
example:
@lisp
(service-extension postgresql-role-service-type
(const (postgresql-role
(name "alice")
(create-database? #t))))
@end lisp
@end deffn
@deftp {Data Type} postgresql-role
PostgreSQL manages database access permissions using the concept of
roles. A role can be thought of as either a database user, or a group
of database users, depending on how the role is set up. Roles can own
database objects (for example, tables) and can assign privileges on
those objects to other roles to control who has access to which objects.
@table @asis
@item @code{name}
The role name.
@item @code{permissions} (default: @code{'(createdb login)})
The role permissions list. Supported permissions are @code{bypassrls},
@code{createdb}, @code{createrole}, @code{login}, @code{replication} and
@code{superuser}.
@item @code{create-database?} (default: @code{#f})
Whether to create a database with the same name as the role.
@end table
@end deftp
@deftp {Data Type} postgresql-role-configuration
Data type representing the configuration of
@var{postgresql-role-service-type}.
@table @asis
@item @code{host} (default: @code{"/var/run/postgresql"})
The PostgreSQL host to connect to.
@item @code{log} (default: @code{"/var/log/postgresql_roles.log"})
File name of the log file.
@item @code{roles} (default: @code{'()})
The initial PostgreSQL roles to create.
@end table
@end deftp
@subsubheading MariaDB/MySQL
@defvr {Scheme Variable} mysql-service-type

View file

@ -58,6 +58,18 @@ (define-module (gnu services databases)
postgresql-service
postgresql-service-type
postgresql-role
postgresql-role?
postgresql-role-name
postgresql-role-permissions
postgresql-role-create-database?
postgresql-role-configuration
postgresql-role-configuration?
postgresql-role-configuration-host
postgresql-role-configuration-roles
postgresql-role-service-type
memcached-service-type
memcached-configuration
memcached-configuration?
@ -343,6 +355,96 @@ (define-deprecated (postgresql-service #:key (postgresql postgresql)
(data-directory data-directory)
(extension-packages extension-packages))))
(define-record-type* <postgresql-role>
postgresql-role make-postgresql-role
postgresql-role?
(name postgresql-role-name) ;string
(permissions postgresql-role-permissions
(default '(createdb login))) ;list
(create-database? postgresql-role-create-database? ;boolean
(default #f)))
(define-record-type* <postgresql-role-configuration>
postgresql-role-configuration make-postgresql-role-configuration
postgresql-role-configuration?
(host postgresql-role-configuration-host ;string
(default "/var/run/postgresql"))
(log postgresql-role-configuration-log ;string
(default "/var/log/postgresql_roles.log"))
(roles postgresql-role-configuration-roles
(default '()))) ;list
(define (postgresql-create-roles config)
;; See: https://www.postgresql.org/docs/current/sql-createrole.html for the
;; complete permissions list.
(define (format-permissions permissions)
(let ((dict '(bypassrls createdb createrole login replication superuser)))
(string-join (filter-map (lambda (permission)
(and (member permission dict)
(string-upcase
(symbol->string permission))))
permissions)
" ")))
(define (roles->queries roles)
(apply mixed-text-file "queries"
(append-map
(lambda (role)
(match-record role <postgresql-role>
(name permissions create-database?)
`("SELECT NOT(EXISTS(SELECT 1 FROM pg_catalog.pg_roles WHERE \
rolname = '" ,name "')) as not_exists;\n"
"\\gset\n"
"\\if :not_exists\n"
"CREATE ROLE " ,name
" WITH " ,(format-permissions permissions)
";\n"
,@(if create-database?
`("CREATE DATABASE " ,name
" OWNER " ,name ";\n")
'())
"\\endif\n")))
roles)))
(let ((host (postgresql-role-configuration-host config))
(roles (postgresql-role-configuration-roles config)))
(program-file
"postgresql-create-roles"
#~(begin
(let ((psql #$(file-append postgresql "/bin/psql")))
(execl psql psql "-a"
"-h" #$host
"-f" #$(roles->queries roles)))))))
(define (postgresql-role-shepherd-service config)
(match-record config <postgresql-role-configuration>
(log)
(list (shepherd-service
(requirement '(postgres))
(provision '(postgres-roles))
(one-shot? #t)
(start #~(make-forkexec-constructor
(list #$(postgresql-create-roles config))
#:user "postgres" #:group "postgres"
#:log-file #$log))
(documentation "Create PostgreSQL roles.")))))
(define postgresql-role-service-type
(service-type (name 'postgresql-role)
(extensions
(list (service-extension shepherd-root-service-type
postgresql-role-shepherd-service)))
(compose concatenate)
(extend (lambda (config extended-roles)
(match-record config <postgresql-role-configuration>
(host roles)
(postgresql-role-configuration
(host host)
(roles (append roles extended-roles))))))
(default-value (postgresql-role-configuration))
(description "Ensure the specified PostgreSQL roles are
created after the PostgreSQL database is started.")))
;;;
;;; Memcached

View file

@ -217,6 +217,9 @@ (define %test-mongodb
(define %postgresql-log-directory
"/var/log/postgresql")
(define %role-log-file
"/var/log/postgresql_roles.log")
(define %postgresql-os
(simple-operating-system
(service postgresql-service-type
@ -229,7 +232,13 @@ (define %postgresql-os
("random_page_cost" 2)
("auto_explain.log_min_duration" "100 ms")
("work_mem" "500 MB")
("debug_print_plan" #t)))))))))
("debug_print_plan" #t)))))))
(service postgresql-role-service-type
(postgresql-role-configuration
(roles
(list (postgresql-role
(name "root")
(create-database? #t))))))))
(define (run-postgresql-test)
"Run tests in %POSTGRESQL-OS."
@ -282,6 +291,39 @@ (define marionette
#t))
marionette))
(test-assert "database ready"
(begin
(marionette-eval
'(begin
(let loop ((i 10))
(unless (or (zero? i)
(and (file-exists? #$%role-log-file)
(string-contains
(call-with-input-file #$%role-log-file
get-string-all)
";\nCREATE DATABASE")))
(sleep 1)
(loop (- i 1)))))
marionette)))
(test-assert "database creation"
(marionette-eval
'(begin
(use-modules (gnu services herd)
(ice-9 popen))
(current-output-port
(open-file "/dev/console" "w0"))
(let* ((port (open-pipe*
OPEN_READ
#$(file-append postgresql "/bin/psql")
"-tAh" "/var/run/postgresql"
"-c" "SELECT 1 FROM pg_database WHERE
datname='root'"))
(output (get-string-all port)))
(close-pipe port)
(string-contains output "1")))
marionette))
(test-end)
(exit (= (test-runner-fail-count (test-runner-current)) 0)))))