mirror of
https://github.com/juspay/services-flake.git
synced 2024-09-19 08:17:11 +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
|
# 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