mirror of
https://github.com/juspay/services-flake.git
synced 2024-09-17 15:28:33 +03:00
Initial implementation: hello and postgres
This commit is contained in:
parent
f156b16946
commit
f171932d13
23
README.md
23
README.md
@ -1,2 +1,23 @@
|
||||
# services-flake
|
||||
NixOS-like services for Nix flakes
|
||||
|
||||
NixOS-like services for Nix flakes, as a [process-compose-flake](https://github.com/Platonic-Systems/process-compose-flake) module (based on flake-parts).
|
||||
|
||||
## Getting Started
|
||||
|
||||
TODO
|
||||
|
||||
## Services available
|
||||
|
||||
- [x] Hello World
|
||||
- [-] PostgreSQL
|
||||
- [ ] MySQL
|
||||
- [ ] Redis
|
||||
- [ ] ...
|
||||
|
||||
## Contributing
|
||||
|
||||
We do not have CI yet, so please run `./test.sh` on **NixOS**.
|
||||
|
||||
## Credits
|
||||
|
||||
Thanks to [the devenv project](https://github.com/cachix/devenv/tree/main/src/modules/services) on which much of our services implementation is based on.
|
10
flake.nix
Normal file
10
flake.nix
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
outputs = _: {
|
||||
processComposeModules.default = ./nix;
|
||||
|
||||
templates.default = {
|
||||
description = "Example flake using process-compose-flake";
|
||||
path = builtins.path { path = ./example; filter = path: _: baseNameOf path == "flake.nix"; };
|
||||
};
|
||||
};
|
||||
}
|
6
nix/default.nix
Normal file
6
nix/default.nix
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
imports = [
|
||||
./hello.nix
|
||||
./postgres.nix
|
||||
];
|
||||
}
|
24
nix/hello.nix
Normal file
24
nix/hello.nix
Normal file
@ -0,0 +1,24 @@
|
||||
{ pkgs, lib, config, ... }:
|
||||
|
||||
{
|
||||
options.services.hello = {
|
||||
enable = lib.mkEnableOption "hello";
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "hello";
|
||||
description = "Process name";
|
||||
};
|
||||
package = lib.mkPackageOption pkgs "hello" { };
|
||||
greeting = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "Hello";
|
||||
description = "The greeting to use";
|
||||
};
|
||||
};
|
||||
config = let cfg = config.services.hello; in lib.mkIf cfg.enable {
|
||||
settings.processes.${cfg.name}.command = ''
|
||||
set -x
|
||||
${lib.getExe cfg.package} -g "${cfg.greeting}"
|
||||
'';
|
||||
};
|
||||
}
|
276
nix/postgres.nix
Normal file
276
nix/postgres.nix
Normal file
@ -0,0 +1,276 @@
|
||||
# Based on https://github.com/cachix/devenv/blob/main/src/modules/services/postgres.nix
|
||||
{ pkgs, lib, config, ... }:
|
||||
|
||||
let
|
||||
inherit (lib) types;
|
||||
in
|
||||
{
|
||||
options.services.postgres = lib.mkOption {
|
||||
description = ''
|
||||
Enable postgresql server
|
||||
'';
|
||||
default = { };
|
||||
type = lib.types.submodule ({ config, ... }: {
|
||||
options = {
|
||||
enable = lib.mkEnableOption "postgres";
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "postgres";
|
||||
description = "Unique process name";
|
||||
};
|
||||
|
||||
package = lib.mkPackageOption pkgs "postgresql" { };
|
||||
extensions = lib.mkOption {
|
||||
type = with types; nullOr (functionTo (listOf package));
|
||||
default = null;
|
||||
example = lib.literalExpression ''
|
||||
extensions: [
|
||||
extensions.pg_cron
|
||||
extensions.postgis
|
||||
extensions.timescaledb
|
||||
];
|
||||
'';
|
||||
description = ''
|
||||
Additional PostgreSQL extensions to install.
|
||||
|
||||
The available extensions are:
|
||||
|
||||
${lib.concatLines (builtins.map (x: "- " + x) (builtins.attrNames pkgs.postgresql.pkgs))}
|
||||
'';
|
||||
};
|
||||
|
||||
dataDir = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "./data/${config.name}";
|
||||
description = "The DB data directory";
|
||||
};
|
||||
listen_addresses = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Listen address";
|
||||
default = "";
|
||||
example = "127.0.0.1";
|
||||
};
|
||||
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 5432;
|
||||
description = ''
|
||||
The TCP port to accept connections.
|
||||
'';
|
||||
};
|
||||
|
||||
createDatabase = lib.mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Create a database named like current user on startup. Only applies when initialDatabases is an empty list.
|
||||
'';
|
||||
};
|
||||
|
||||
initdbArgs = lib.mkOption {
|
||||
type = types.listOf types.lines;
|
||||
default = [ "--locale=C" "--encoding=UTF8" ];
|
||||
example = [ "--data-checksums" "--allow-group-access" ];
|
||||
description = ''
|
||||
Additional arguments passed to `initdb` during data dir
|
||||
initialisation.
|
||||
'';
|
||||
};
|
||||
|
||||
settings = lib.mkOption {
|
||||
type = with lib.types; attrsOf (oneOf [ bool float int str ]);
|
||||
default = { };
|
||||
description = ''
|
||||
PostgreSQL configuration. Refer to
|
||||
<https://www.postgresql.org/docs/11/config-setting.html#CONFIG-SETTING-CONFIGURATION-FILE>
|
||||
for an overview of `postgresql.conf`.
|
||||
|
||||
String values will automatically be enclosed in single quotes. Single quotes will be
|
||||
escaped with two single quotes as described by the upstream documentation linked above.
|
||||
'';
|
||||
default = {
|
||||
listen_addresses = config.listen_addresses;
|
||||
port = config.port;
|
||||
unix_socket_directories = lib.mkDefault config.dataDir;
|
||||
};
|
||||
example = lib.literalExpression ''
|
||||
{
|
||||
log_connections = true;
|
||||
log_statement = "all";
|
||||
logging_collector = true
|
||||
log_disconnections = true
|
||||
log_destination = lib.mkForce "syslog";
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
initialDatabases = lib.mkOption {
|
||||
type = types.listOf (types.submodule {
|
||||
options = {
|
||||
name = lib.mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
The name of the database to create.
|
||||
'';
|
||||
};
|
||||
schema = lib.mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
The initial schema of the database; if null (the default),
|
||||
an empty database is created.
|
||||
'';
|
||||
};
|
||||
};
|
||||
});
|
||||
default = [ ];
|
||||
description = ''
|
||||
List of database names and their initial schemas that should be used to create databases on the first startup
|
||||
of Postgres. The schema attribute is optional: If not specified, an empty database is created.
|
||||
'';
|
||||
example = lib.literalExpression ''
|
||||
[
|
||||
{
|
||||
name = "foodatabase";
|
||||
schema = ./foodatabase.sql;
|
||||
}
|
||||
{ name = "bardatabase"; }
|
||||
]
|
||||
'';
|
||||
};
|
||||
|
||||
initialScript = lib.mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Initial SQL commands to run during database initialization. This can be multiple
|
||||
SQL expressions separated by a semi-colon.
|
||||
'';
|
||||
example = lib.literalExpression ''
|
||||
CREATE USER postgres SUPERUSER;
|
||||
CREATE USER bar;
|
||||
'';
|
||||
};
|
||||
};
|
||||
});
|
||||
};
|
||||
config = let cfg = config.services.postgres; in lib.mkIf cfg.enable {
|
||||
settings.processes =
|
||||
let
|
||||
postgresPkg =
|
||||
if cfg.extensions != null then
|
||||
if builtins.hasAttr "withPackages" cfg.package
|
||||
then cfg.package.withPackages cfg.extensions
|
||||
else
|
||||
builtins.throw ''
|
||||
Cannot add extensions to the PostgreSQL package.
|
||||
`services.postgres.package` is missing the `withPackages` attribute. Did you already add extensions to the package?
|
||||
''
|
||||
else cfg.package;
|
||||
in
|
||||
{
|
||||
# DB initialization
|
||||
"${cfg.name}-init".command =
|
||||
let
|
||||
setupInitialDatabases =
|
||||
if cfg.initialDatabases != [ ] then
|
||||
(lib.concatMapStrings
|
||||
(database: ''
|
||||
echo "Checking presence of database: ${database.name}"
|
||||
# Create initial databases
|
||||
dbAlreadyExists="$(
|
||||
echo "SELECT 1 as exists FROM pg_database WHERE datname = '${database.name}';" | \
|
||||
postgres --single -E postgres | \
|
||||
${pkgs.gnugrep}/bin/grep -c 'exists = "1"' || true
|
||||
)"
|
||||
echo $dbAlreadyExists
|
||||
if [ 1 -ne "$dbAlreadyExists" ]; then
|
||||
echo "Creating database: ${database.name}"
|
||||
echo 'create database "${database.name}";' | postgres --single -E postgres
|
||||
|
||||
|
||||
${lib.optionalString (database.schema != null) ''
|
||||
echo "Applying database schema on ${database.name}"
|
||||
if [ -f "${database.schema}" ]
|
||||
then
|
||||
echo "Running file ${database.schema}"
|
||||
${pkgs.gawk}/bin/awk 'NF' "${database.schema}" | postgres --single -j -E ${database.name}
|
||||
elif [ -d "${database.schema}" ]
|
||||
then
|
||||
# Read sql files in version order. Apply one file
|
||||
# at a time to handle files where the last statement
|
||||
# doesn't end in a ;.
|
||||
ls -1v "${database.schema}"/*.sql | while read f ; do
|
||||
echo "Applying sql file: $f"
|
||||
${pkgs.gawk}/bin/awk 'NF' "$f" | postgres --single -j -E ${database.name}
|
||||
done
|
||||
else
|
||||
echo "ERROR: Could not determine how to apply schema with ${database.schema}"
|
||||
exit 1
|
||||
fi
|
||||
''}
|
||||
fi
|
||||
'')
|
||||
cfg.initialDatabases)
|
||||
else
|
||||
lib.optionalString cfg.createDatabase ''
|
||||
echo "CREATE DATABASE ''${USER:-$(id -nu)};" | postgres --single -E postgres '';
|
||||
|
||||
runInitialScript =
|
||||
if cfg.initialScript != null then
|
||||
''
|
||||
echo "${cfg.initialScript}" | postgres --single -E postgres
|
||||
''
|
||||
else
|
||||
"";
|
||||
|
||||
toStr = value:
|
||||
if true == value then
|
||||
"yes"
|
||||
else if false == value then
|
||||
"no"
|
||||
else if lib.isString value then
|
||||
"'${lib.replaceStrings [ "'" ] [ "''" ] value}'"
|
||||
else
|
||||
toString value;
|
||||
|
||||
configFile = pkgs.writeText "postgresql.conf" (lib.concatStringsSep "\n"
|
||||
(lib.mapAttrsToList (n: v: "${n} = ${toStr v}") cfg.settings));
|
||||
|
||||
setupScript = pkgs.writeShellScriptBin "setup-postgres" ''
|
||||
set -euo pipefail
|
||||
export PATH=${postgresPkg}/bin:${pkgs.coreutils}/bin
|
||||
|
||||
if [[ ! -d "$PGDATA" ]]; then
|
||||
initdb ${lib.concatStringsSep " " cfg.initdbArgs}
|
||||
${setupInitialDatabases}
|
||||
|
||||
${runInitialScript}
|
||||
fi
|
||||
|
||||
# Setup config
|
||||
cp ${configFile} "$PGDATA/postgresql.conf"
|
||||
'';
|
||||
in
|
||||
''
|
||||
export PGDATA="${cfg.dataDir}"
|
||||
${lib.getExe setupScript}
|
||||
'';
|
||||
|
||||
# DB process
|
||||
${cfg.name} = {
|
||||
command = ''
|
||||
set -x
|
||||
export PATH="${postgresPkg}"/bin:$PATH
|
||||
export LOCKDIR="/tmp"
|
||||
postgres -k $LOCKDIR -D ${cfg.dataDir}
|
||||
'';
|
||||
depends_on."${cfg.name}-init".condition = "process_completed_successfully";
|
||||
# SIGINT (= 2) for faster shutdown: https://www.postgresql.org/docs/current/server-shutdown.html
|
||||
shutdown.signal = 2;
|
||||
# https://github.com/F1bonacc1/process-compose#-auto-restart-if-not-healthy
|
||||
availability.restart = "on_failure";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
12
nix/postgres_test.nix
Normal file
12
nix/postgres_test.nix
Normal file
@ -0,0 +1,12 @@
|
||||
{ config, ... }: {
|
||||
services.postgres = {
|
||||
enable = true;
|
||||
listen_addresses = "127.0.0.1";
|
||||
};
|
||||
testScript = ''
|
||||
process_compose.wait_until(lambda procs:
|
||||
procs["postgres"]["status"] == "Running"
|
||||
)
|
||||
machine.succeed("echo 'SELECT version();' | ${config.services.postgres.package}/bin/psql -h 127.0.0.1 -U tester")
|
||||
'';
|
||||
}
|
9
test.sh
Executable file
9
test.sh
Executable file
@ -0,0 +1,9 @@
|
||||
set -euxo pipefail
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# On NixOS, run the VM tests to test runtime behaviour
|
||||
if command -v nixos-rebuild &> /dev/null; then
|
||||
# service tests
|
||||
nix flake check -L ./test
|
||||
fi
|
96
test/flake.lock
Normal file
96
test/flake.lock
Normal file
@ -0,0 +1,96 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-parts": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1685662779,
|
||||
"narHash": "sha256-cKDDciXGpMEjP1n6HlzKinN0H+oLmNpgeCTzYnsA2po=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "71fb97f0d875fd4de4994dfb849f2c75e17eb6c3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1686089707,
|
||||
"narHash": "sha256-LTNlJcru2qJ0XhlhG9Acp5KyjB774Pza3tRH0pKIb3o=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "af21c31b2a1ec5d361ed8050edd0303c31306397",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"dir": "lib",
|
||||
"lastModified": 1685564631,
|
||||
"narHash": "sha256-8ywr3AkblY4++3lIVxmrWZFzac7+f32ZEhH/A8pNscI=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "4f53efe34b3a8877ac923b9350c874e3dcd5dc0a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"dir": "lib",
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"process-compose-flake": {
|
||||
"locked": {
|
||||
"lastModified": 1687194805,
|
||||
"narHash": "sha256-DAqMJFWXft+eWrPOLWrP4xdK1DQELWkkU47PM+ODG2U=",
|
||||
"owner": "Platonic-Systems",
|
||||
"repo": "process-compose-flake",
|
||||
"rev": "dd1dff00700deb2bc4afa44e7082c0176c095fd9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "Platonic-Systems",
|
||||
"repo": "process-compose-flake",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-parts": "flake-parts",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"process-compose-flake": "process-compose-flake",
|
||||
"systems": "systems"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
25
test/flake.nix
Normal file
25
test/flake.nix
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
|
||||
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||
systems.url = "github:nix-systems/default";
|
||||
process-compose-flake.url = "github:Platonic-Systems/process-compose-flake";
|
||||
};
|
||||
outputs = inputs:
|
||||
inputs.flake-parts.lib.mkFlake { inherit inputs; } {
|
||||
systems = import inputs.systems;
|
||||
imports = [
|
||||
inputs.process-compose-flake.flakeModule
|
||||
];
|
||||
perSystem = { self', pkgs, lib, ... }: {
|
||||
process-compose = {
|
||||
postgres = {
|
||||
imports = [
|
||||
inputs.process-compose-flake.processComposeModules.services
|
||||
../nix/postgres_test.nix
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue
Block a user