From 5e48c5a965290845f4d42cc17216231ca910ab39 Mon Sep 17 00:00:00 2001 From: Abhishek Singh Date: Thu, 29 Feb 2024 18:10:22 +0530 Subject: [PATCH] add pgAdmin service (#115) --- doc/pgadmin.md | 18 +++++ doc/services.md | 1 + nix/default.nix | 1 + nix/pgadmin.nix | 171 +++++++++++++++++++++++++++++++++++++++++++ nix/pgadmin_test.nix | 23 ++++++ test/flake.nix | 7 ++ 6 files changed, 221 insertions(+) create mode 100644 doc/pgadmin.md create mode 100644 nix/pgadmin.nix create mode 100644 nix/pgadmin_test.nix diff --git a/doc/pgadmin.md b/doc/pgadmin.md new file mode 100644 index 0000000..a843e20 --- /dev/null +++ b/doc/pgadmin.md @@ -0,0 +1,18 @@ +# pgAdmin + +[pgAdmin] is a feature rich Open Source administration and development platform for PostgreSQL. + +[pgAdmin]: https://www.pgadmin.org/ + +## Getting Started + +```nix +# In `perSystem.process-compose.` +{ + services.pgadmin."pgad1" = { + enable = true; + initialEmail = "email@gmail.com"; + initialPassword = "password"; + }; +} +``` diff --git a/doc/services.md b/doc/services.md index 9b988f6..3334f21 100644 --- a/doc/services.md +++ b/doc/services.md @@ -18,6 +18,7 @@ short-title: Services - [ ] Zookeeper - [x] [[grafana]]# - [X] [[prometheus]]# +- [X] [[pgadmin]]# - [ ] ... [gh]: https://github.com/juspay/services-flake diff --git a/nix/default.nix b/nix/default.nix index 5e65d1a..0ae9f0a 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -15,5 +15,6 @@ in ./zookeeper.nix ./grafana.nix ./prometheus.nix + ./pgadmin.nix ]; } diff --git a/nix/pgadmin.nix b/nix/pgadmin.nix new file mode 100644 index 0000000..473528d --- /dev/null +++ b/nix/pgadmin.nix @@ -0,0 +1,171 @@ +# Based on https://github.com/NixOS/nixpkgs/blob/d53c2037394da6fe98decca417fc8fda64bf2443/nixos/modules/services/admin/pgadmin.nix +{ pkgs, lib, name, config, ... }: +let + inherit (lib) types; + + _base = with types; [ int bool str ]; + base = with types; oneOf ([ (listOf (oneOf _base)) (attrsOf (oneOf _base)) ] ++ _base); + + formatAttrset = attr: + "{${lib.concatStringsSep "\n" (lib.mapAttrsToList (key: value: "${builtins.toJSON key}: ${formatPyValue value},") attr)}}"; + + formatPyValue = value: + if builtins.isString value then builtins.toJSON value + else if value ? _expr then value._expr + else if builtins.isInt value then toString value + else if builtins.isBool value then (if value then "True" else "False") + else if builtins.isAttrs value then (formatAttrset value) + else if builtins.isList value then "[${lib.concatStringsSep "\n" (map (v: "${formatPyValue v},") value)}]" + else throw "Unrecognized type"; + + formatPy = attrs: + lib.concatStringsSep "\n" (lib.mapAttrsToList (key: value: "${key} = ${formatPyValue value}") attrs); + + pyType = with types; attrsOf (oneOf [ (attrsOf base) (listOf base) base ]); +in +{ + options = { + enable = lib.mkEnableOption name; + + package = lib.mkPackageOption pkgs "pgadmin4" { }; + + host = lib.mkOption { + description = lib.mdDoc "Host for pgadmin4 to run on"; + type = types.str; + default = "localhost"; + }; + + port = lib.mkOption { + description = lib.mdDoc "Port for pgadmin4 to run on"; + type = types.port; + default = 5050; + }; + + + initialEmail = lib.mkOption { + description = "Initial email for the pgAdmin account"; + type = types.str; + }; + + initialPassword = lib.mkOption { + description = "Initial password for the pgAdmin account"; + type = types.str; + }; + + minimumPasswordLength = lib.mkOption { + description = "Minimum length of the password"; + type = types.int; + default = 6; + }; + + dataDir = lib.mkOption { + type = types.str; + default = "./data/${name}"; + description = "The pgadmin4 data directory"; + }; + + extraDefaultConfig = lib.mkOption { + type = pyType; + internal = true; + readOnly = true; + default = { + DATA_DIR = { + _expr = "os.getenv('PGADMIN_DATADIR')"; + }; + DEFAULT_SERVER = config.host; + DEFAULT_SERVER_PORT = config.port; + PASSWORD_LENGTH_MIN = config.minimumPasswordLength; + SERVER_MODE = true; + UPGRADE_CHECK_ENABLED = false; + }; + }; + + extraConfig = lib.mkOption { + type = pyType; + default = { }; + description = "Additional config for pgadmin4"; + }; + + outputs.settings = lib.mkOption { + type = types.deferredModule; + internal = true; + readOnly = true; + default = { + processes = + let + pgadminConfig = pkgs.writeTextDir "config_local.py" ( + "import os\n" + (formatPy + (lib.recursiveUpdate config.extraDefaultConfig config.extraConfig)) + ); + in + { + "${name}-init" = + let + setupScript = pkgs.writeShellApplication { + name = "setup-pgadmin"; + runtimeInputs = [ config.package ]; + text = '' + export PYTHONPATH="${pgadminConfig}" + PGADMIN_DATADIR="$(readlink -m ${config.dataDir})" + mkdir -p "$PGADMIN_DATADIR" + export PGADMIN_DATADIR + PGADMIN_SETUP_EMAIL=${lib.escapeShellArg config.initialEmail} + export PGADMIN_SETUP_EMAIL + PGADMIN_SETUP_PASSWORD=${lib.escapeShellArg config.initialPassword} + export PGADMIN_SETUP_PASSWORD + if [ -f ${config.package}/bin/.pgadmin4-setup-wrapped ]; then + # pgadmin-7.5 has .pgadmin4-setup-wrapped + ${config.package}/bin/.pgadmin4-setup-wrapped setup + else + # pgadmin-8.2 has .pgadmin4-cli-wrapped + ${config.package}/bin/.pgadmin4-cli-wrapped setup + fi + ''; + }; + in + { + command = setupScript; + namespace = name; + }; + + "${name}" = + let + startScript = pkgs.writeShellApplication { + name = "start-pgadmin"; + runtimeInputs = [ config.package ]; + text = '' + export PYTHONPATH="${pgadminConfig}" + PGADMIN_DATADIR="$(readlink -m ${config.dataDir})" + export PGADMIN_DATADIR + PGADMIN_SETUP_EMAIL=${lib.escapeShellArg config.initialEmail} + export PGADMIN_SETUP_EMAIL + PGADMIN_SETUP_PASSWORD=${lib.escapeShellArg config.initialPassword} + export PGADMIN_SETUP_PASSWORD + ${config.package}/bin/.pgadmin4-wrapped + ''; + }; + in + { + command = startScript; + readiness_probe = { + http_get = { + host = config.host; + port = config.port; + path = "/misc/ping"; + }; + initial_delay_seconds = 2; + period_seconds = 10; + timeout_seconds = 4; + success_threshold = 1; + failure_threshold = 5; + }; + namespace = name; + depends_on."${name}-init".condition = "process_completed_successfully"; + # https://github.com/F1bonacc1/process-compose#-auto-restart-if-not-healthy + availability.restart = "on_failure"; + }; + }; + }; + }; + }; +} diff --git a/nix/pgadmin_test.nix b/nix/pgadmin_test.nix new file mode 100644 index 0000000..b070b94 --- /dev/null +++ b/nix/pgadmin_test.nix @@ -0,0 +1,23 @@ +{ pkgs, config, ... }: { + + services.pgadmin."pgad1" = { + enable = true; + initialEmail = "email@gmail.com"; + initialPassword = "password"; + }; + + settings.processes.test = + let + cfg = config.services.pgadmin."pgad1"; + in + { + command = pkgs.writeShellApplication { + runtimeInputs = [ cfg.package pkgs.curl pkgs.gnugrep ]; + text = '' + curl http://localhost:5050/misc/ping | grep "PING" + ''; + name = "pgadmin-test"; + }; + depends_on."pgad1".condition = "process_healthy"; + }; +} diff --git a/test/flake.nix b/test/flake.nix index f40b510..182ec76 100644 --- a/test/flake.nix +++ b/test/flake.nix @@ -17,6 +17,12 @@ inherit system; # Required for elastic search config.allowUnfree = true; + overlays = [ + (final: prev: { + # Because tests are failing on darwin: https://github.com/juspay/services-flake/pull/115#issuecomment-1970467684 + pgadmin4 = prev.pgadmin4.overrideAttrs (_: { doInstallCheck = false; }); + }) + ]; }; process-compose = let @@ -48,6 +54,7 @@ "${inputs.services-flake}/nix/zookeeper_test.nix" "${inputs.services-flake}/nix/grafana_test.nix" "${inputs.services-flake}/nix/prometheus_test.nix" + "${inputs.services-flake}/nix/pgadmin_test.nix" ]); }; };