-
-
Notifications
You must be signed in to change notification settings - Fork 18.2k
[WIP]: PostgreSQL declarative databases #72365
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,8 +28,86 @@ in | |
|
|
||
| ###### interface | ||
|
|
||
| options = { | ||
|
|
||
| options = let | ||
| databaseOpts = { | ||
| options = { | ||
| name = mkOption { | ||
| type = types.str; | ||
| description = '' | ||
| The name of the database to ensure. | ||
| ''; | ||
| example = "gitlab"; | ||
| }; | ||
| owner = mkOption { | ||
| type = with types; nullOr str; | ||
| default = null; | ||
| description = '' | ||
| The name of the database owner. | ||
|
|
||
| The user will be created if it doesn't already | ||
| exist. The owner of an existing database will be | ||
| changed if it's changed here, and all objects in the | ||
| database owned by the previous owner will be | ||
| reassigned to the new one. | ||
|
|
||
| See | ||
| <link xlink:href="https://www.postgresql.org/docs/current/manage-ag-createdb.html">Creating a Database</link>, | ||
| <link xlink:href="https://www.postgresql.org/docs/current/sql-reassign-owned.html">REASSIGN OWNED</link> | ||
| and | ||
| <link xlink:href="https://www.postgresql.org/docs/current/sql-alterdatabase.html">ALTER DATABASE</link> | ||
| for more details. | ||
| ''; | ||
| }; | ||
| extensions = mkOption { | ||
| type = with types; listOf str; | ||
| default = []; | ||
| description = '' | ||
| Extensions to add to the database. | ||
|
|
||
| Extensions will be added to existing databases, but | ||
| not removed if they're removed from the list. | ||
|
|
||
| See | ||
| <link xlink:href="https://www.postgresql.org/docs/current/sql-createextension.html"> | ||
| CREATE EXTENSION</link> for more details. | ||
| ''; | ||
| example = [ "pg_trgm" "hstore" ]; | ||
| }; | ||
| }; | ||
| }; | ||
| userOpts = { | ||
| options = { | ||
| name = mkOption { | ||
| type = types.str; | ||
| description = '' | ||
| Name of the user to ensure. | ||
| ''; | ||
| }; | ||
| ensurePermissions = mkOption { | ||
| type = types.attrsOf types.str; | ||
| default = {}; | ||
| description = '' | ||
| Permissions to ensure for the user, specified as an attribute set. | ||
| The attribute names specify the database and tables to grant the permissions for. | ||
| The attribute values specify the permissions to grant. You may specify one or | ||
| multiple comma-separated SQL privileges here. | ||
|
|
||
| For more information on how to specify the target | ||
| and on which privileges exist, see the | ||
| <link xlink:href="https://www.postgresql.org/docs/current/sql-grant.html">GRANT syntax</link>. | ||
| The attributes are used as <code>GRANT ''${attrName} ON ''${attrValue}</code>. | ||
| ''; | ||
| example = literalExample '' | ||
| { | ||
| "DATABASE nextcloud" = "ALL PRIVILEGES"; | ||
| "ALL TABLES IN SCHEMA public" = "ALL PRIVILEGES"; | ||
| } | ||
| ''; | ||
| }; | ||
| }; | ||
| }; | ||
| in | ||
| { | ||
| services.postgresql = { | ||
|
|
||
| enable = mkOption { | ||
|
|
@@ -97,13 +175,23 @@ in | |
| }; | ||
|
|
||
| ensureDatabases = mkOption { | ||
| type = types.listOf types.str; | ||
| type = with types; | ||
| listOf (coercedTo str | ||
| (name: { inherit name; }) | ||
| (submodule databaseOpts)); | ||
| default = []; | ||
| description = '' | ||
| Ensures that the specified databases exist. | ||
| This option will never delete existing databases, especially not when the value of this | ||
| option is changed. This means that databases created once through this option or | ||
| otherwise have to be removed manually. | ||
| Ensure the existence of the listed databases with their | ||
| respective owner and extensions. | ||
|
|
||
| This option will never delete existing databases; if the | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. needs to be adjusted |
||
| name of a database is changed here, a new database with that | ||
| name will be created and the old one will remain. Databases | ||
| created once through this option or otherwise have to be | ||
| removed manually. | ||
|
|
||
| Extensions will be added to existing databases, but not | ||
| removed if they're removed from the list. | ||
| ''; | ||
| example = [ | ||
| "gitea" | ||
|
|
@@ -112,37 +200,7 @@ in | |
| }; | ||
|
|
||
| ensureUsers = mkOption { | ||
| type = types.listOf (types.submodule { | ||
| options = { | ||
| name = mkOption { | ||
| type = types.str; | ||
| description = '' | ||
| Name of the user to ensure. | ||
| ''; | ||
| }; | ||
| ensurePermissions = mkOption { | ||
| type = types.attrsOf types.str; | ||
| default = {}; | ||
| description = '' | ||
| Permissions to ensure for the user, specified as an attribute set. | ||
| The attribute names specify the database and tables to grant the permissions for. | ||
| The attribute values specify the permissions to grant. You may specify one or | ||
| multiple comma-separated SQL privileges here. | ||
|
|
||
| For more information on how to specify the target | ||
| and on which privileges exist, see the | ||
| <link xlink:href="https://www.postgresql.org/docs/current/sql-grant.html">GRANT syntax</link>. | ||
| The attributes are used as <code>GRANT ''${attrName} ON ''${attrValue}</code>. | ||
| ''; | ||
| example = literalExample '' | ||
| { | ||
| "DATABASE nextcloud" = "ALL PRIVILEGES"; | ||
| "ALL TABLES IN SCHEMA public" = "ALL PRIVILEGES"; | ||
| } | ||
| ''; | ||
| }; | ||
| }; | ||
| }); | ||
| type = with types; listOf (submodule userOpts); | ||
| default = []; | ||
| description = '' | ||
| Ensures that the specified users exist and have at least the ensured permissions. | ||
|
|
@@ -320,6 +378,7 @@ in | |
| # Wait for PostgreSQL to be ready to accept connections. | ||
| postStart = | ||
| '' | ||
| set -eu | ||
| PSQL="${pkgs.sudo}/bin/sudo -u ${cfg.superUser} psql --port=${toString cfg.port}" | ||
|
|
||
| while ! $PSQL -d postgres -c "" 2> /dev/null; do | ||
|
|
@@ -333,18 +392,51 @@ in | |
| ''} | ||
| rm -f "${cfg.dataDir}/.first_startup" | ||
| fi | ||
| '' + optionalString (cfg.ensureDatabases != []) '' | ||
| ${concatMapStrings (database: '' | ||
| $PSQL -tAc "SELECT 1 FROM pg_database WHERE datname = '${database}'" | grep -q 1 || $PSQL -tAc 'CREATE DATABASE "${database}"' | ||
| '') cfg.ensureDatabases} | ||
| '' + '' | ||
| ${concatMapStrings (user: '' | ||
| $PSQL -tAc "SELECT 1 FROM pg_roles WHERE rolname='${user.name}'" | grep -q 1 || $PSQL -tAc "CREATE USER ${user.name}" | ||
| '' | ||
| + concatMapStrings (user: '' | ||
| $PSQL -tAc "SELECT 1 FROM pg_roles WHERE rolname='${user}'" | grep -q 1 || $PSQL -tAc "CREATE USER ${user}" | ||
| '') (unique (concatMap (a: if a ? owner then if a.owner != null then [a.owner] else [] else [a.name]) (cfg.ensureUsers ++ cfg.ensureDatabases))) | ||
| + concatMapStrings (db: '' | ||
| database_exists=$($PSQL -tAc "SELECT 'true' FROM pg_database WHERE datname = '${db.name}'") | ||
| if [[ "$database_exists" != "true" ]]; then | ||
| $PSQL -tAc 'CREATE DATABASE "${db.name}" ${optionalString (db.owner != null) ''OWNER "${db.owner}"''}' | ||
| fi | ||
|
|
||
| echo "${db.name}" >> "${cfg.dataDir}/databases" | ||
|
|
||
| ${optionalString (db.owner != null) '' | ||
| current_owner=$($PSQL -tAc "SELECT pg_catalog.pg_get_userbyid(datdba) FROM pg_catalog.pg_database WHERE datname = '${db.name}'") | ||
| if [[ "$current_owner" != "${db.owner}" ]]; then | ||
| $PSQL -tAc 'ALTER DATABASE "${db.name}" OWNER TO "${db.owner}"' | ||
| if [[ -e "${cfg.dataDir}/.reassigning_${db.name}" ]]; then | ||
| echo "Reassigning ownership of ${db.name} to ${db.owner} failed on last boot. Failing..." | ||
| exit 1 | ||
| fi | ||
| touch "${cfg.dataDir}/.reassigning_${db.name}" | ||
| $PSQL "${db.name}" -tAc "REASSIGN OWNED BY \"$current_owner\" TO \"${db.owner}\"" | ||
| rm "${cfg.dataDir}/.reassigning_${db.name}" | ||
| fi | ||
| ''} | ||
| '' | ||
| + concatMapStrings (extension: '' | ||
| $PSQL '${db.name}' -tAc "CREATE EXTENSION IF NOT EXISTS ${extension}" | ||
| '') db.extensions | ||
| ) cfg.ensureDatabases | ||
| + '' | ||
| touch "${cfg.dataDir}/databases" | ||
| if [[ -f "${cfg.dataDir}/existing_databases" ]]; then | ||
| for db in $(grep -vFxf "${cfg.dataDir}/databases" "${cfg.dataDir}/existing_databases" || echo ""); do | ||
| echo "Dropping database $db!" | ||
| $PSQL -tAc "DROP DATABASE \"$db\"" | ||
| done | ||
| fi | ||
| mv "${cfg.dataDir}/databases" "${cfg.dataDir}/existing_databases" | ||
| '' | ||
| + concatMapStrings (user: '' | ||
| ${concatStringsSep "\n" (mapAttrsToList (database: permission: '' | ||
| $PSQL -tAc 'GRANT ${permission} ON ${database} TO ${user.name}' | ||
| '') user.ensurePermissions)} | ||
| '') cfg.ensureUsers} | ||
| ''; | ||
| '') cfg.ensureUsers; | ||
|
|
||
| unitConfig.RequiresMountsFor = "${cfg.dataDir}"; | ||
| }; | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This option seems to be on the wrong level.
Extensions in Postgres do not exist on a database level.
In fact, you cannot even have the same extension loaded in different versions on different schemas, even though the documentation might suggest that.
Because of that, I think this option belongs on the service layer, not on a database specific layer in the options.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, that doesn't sound right. I'd have to test this myself to confirm, but if that's true, the documentation is plain wrong. Refer to https://www.postgresql.org/docs/current/sql-createextension.html - the first sentence is
CREATE EXTENSION loads a new extension into the current database.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not impossible I am remembering this from an older version, but I have this nagging feeling that something was iffy here. Best to test, I agree.