Merge pull request #16 from Platonic-Systems/submodule

Add submodule type for YAML format
This commit is contained in:
Sridhar Ratnakumar 2023-06-09 17:13:57 -04:00 committed by GitHub
commit e46b51dc23
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 380 additions and 68 deletions

View File

@ -19,8 +19,11 @@
# This adds a `self.packages.default`
process-compose."default" = {
settings = {
processes = {
environment = {
DATAFILE = "data.sqlite";
};
processes = {
# Print a pony every 2 seconds, because why not.
ponysay.command = ''
while true; do
@ -30,17 +33,23 @@
'';
# Create .sqlite database from chinook database.
sqlite-init.command = ''
echo "`date`: Importing Chinook database..."
${lib.getExe pkgs.sqlite} data.sqlite < ${inputs.chinookDb}/ChinookDatabase/DataSources/Chinook_Sqlite.sql
echo "`date`: Done."
'';
sqlite-init.command = pkgs.writeShellApplication {
name = "sqlite-init";
text = ''
echo "$(date): Importing Chinook database ($DATAFILE) ..."
${lib.getExe pkgs.sqlite} "$DATAFILE" < ${inputs.chinookDb}/ChinookDatabase/DataSources/Chinook_Sqlite.sql
echo "$(date): Done."
'';
};
# Run sqlite-web on the local chinook database.
sqlite-web = {
command = ''
${pkgs.sqlite-web}/bin/sqlite_web data.sqlite
'';
command = pkgs.writeShellApplication {
name = "sqlite-web";
text = ''
${pkgs.sqlite-web}/bin/sqlite_web "$DATAFILE"
'';
};
# The 'depends_on' will have this process wait until the above one is completed.
depends_on."sqlite-init".condition = "process_completed_successfully";
};

View File

@ -1,5 +1,5 @@
{
outputs = { self, ... }: {
outputs = _: {
flakeModule = ./nix/flake-module.nix;
templates.default = {

View File

@ -6,7 +6,7 @@ in
{
imports = [
./cli.nix
./settings.nix
./settings
];
options = {
@ -24,6 +24,13 @@ in
The final package that will run 'process-compose up' for this configuration.
'';
};
debug = mkOption {
type = types.bool;
default = false;
description = ''
Whether to dump the process-compose YAML file at start.
'';
};
};
config.outputs.package =
@ -31,6 +38,7 @@ in
inherit name;
runtimeInputs = [ config.package ];
text = ''
${if config.debug then "cat ${config.outputs.settingsYaml}" else ""}
process-compose up \
-f ${config.outputs.settingsYaml} \
${config.outputs.upCommandArgs} \

View File

@ -1,45 +0,0 @@
{ name, config, pkgs, lib, ... }:
let
inherit (lib) types mkOption literalExpression;
in
{
options = {
settings = mkOption {
type = (pkgs.formats.yaml { }).type;
example =
# packages.${system}.watch-server becomes available
# execute `nix run .#watch-server` or incude packages.${system}.watch-server
# as a nativeBuildInput to your devShell
literalExpression ''
{
watch-server = {
processes = {
backend = "''${pkgs.simple-http-server}";
frontend = "''${pkgs.simple-http-server}";
};
};
};
'';
description = ''
For each attribute `x = process-compose config` a flake app and package `x` is added to the flake.
Which runs process-compose with the declared config.
'';
};
outputs.settingsYaml = mkOption {
type = types.attrsOf types.raw;
internal = true;
};
};
config.outputs.settingsYaml =
let
toYAMLFile =
attrs:
pkgs.runCommand "toYamlFile" { buildInputs = [ pkgs.yq-go ]; } ''
yq -P '.' ${pkgs.writeTextFile { name = "process-compose-${name}.json"; text = (builtins.toJSON attrs); }} > $out
'';
in
toYAMLFile config.settings;
}

View File

@ -0,0 +1,9 @@
{ lib, ... }:
args:
lib.mkOption (args // {
type = lib.types.either lib.types.package lib.types.str;
apply = pkg:
if builtins.isString pkg then pkg else
lib.getExe pkg;
})

View File

@ -0,0 +1,116 @@
{ name, config, pkgs, lib, ... }:
let
inherit (lib) types mkOption literalExpression;
submoduleWithPkgs = mod:
types.submoduleWith {
specialArgs = { inherit pkgs lib; };
modules = [ mod ];
};
in
{
options = {
settings = mkOption {
type = types.submodule {
options = {
processes = mkOption {
type = types.attrsOf (submoduleWithPkgs ./process.nix);
default = { };
description = ''
A map of process names to their configuration.
'';
};
environment = import ./environment.nix { inherit lib; };
log_length = mkOption {
type = types.nullOr types.ints.unsigned;
default = null;
example = 3000;
};
log_level = mkOption {
type = types.nullOr (types.enum [
"trace"
"debug"
"info"
"warn"
"error"
"fatal"
"panic"
]);
default = null;
example = "info";
};
log_location = mkOption {
type = types.nullOr types.str;
default = null;
example = "./pc.log";
};
shell = {
shell_argument = mkOption {
type = types.str;
default = "-c";
example = "-c";
};
shell_command = import ./command.nix { inherit lib; } {
description = ''
The shell to use to run the process `command`s.
For reproducibility across systems, by default this uses
`pkgs.bash`.
'';
default = pkgs.bash;
};
};
version = mkOption {
type = types.nullOr types.str;
default = null;
example = "0.5";
};
};
};
example =
# packages.${system}.watch-server becomes available
# execute `nix run .#watch-server` or incude packages.${system}.watch-server
# as a nativeBuildInput to your devShell
literalExpression ''
{
watch-server = {
processes = {
backend = "''${pkgs.simple-http-server}";
frontend = "''${pkgs.simple-http-server}";
};
};
};
'';
description = ''
For each attribute `x = process-compose config` a flake app and package `x` is added to the flake.
Which runs process-compose with the declared config.
'';
};
outputs.settingsYaml = mkOption {
type = types.attrsOf types.raw;
internal = true;
};
};
config.outputs.settingsYaml =
let
removeNullAndEmptyAttrs = attrs:
let
f = lib.filterAttrsRecursive (key: value: value != null && value != { });
# filterAttrsRecursive doesn't delete the *resulting* empty attrs, so we must
# evaluate it again and to get rid of it.
in
lib.pipe attrs [ f f ];
toYAMLFile =
attrs:
pkgs.runCommand "${name}.yaml" { buildInputs = [ pkgs.yq-go ]; } ''
yq -oy -P '.' ${pkgs.writeTextFile { name = "process-compose-${name}.json"; text = (builtins.toJSON attrs); }} > $out
'';
in
toYAMLFile (removeNullAndEmptyAttrs config.settings);
}

View File

@ -0,0 +1,20 @@
{ lib, ... }:
let
inherit (lib) types;
inherit (types) nullOr either listOf str attrsOf;
in
lib.mkOption {
type =
nullOr
(either (listOf str) (attrsOf str));
default = null;
example = { ABC="2221"; PRINT_ERR="111"; };
description = ''
Attrset of environment variables.
List of strings is also allowed.
'';
apply = attrs:
if ! builtins.isAttrs attrs then attrs else
lib.mapAttrsToList (name: value: "${name}=${value}") attrs;
}

View File

@ -0,0 +1,68 @@
{ lib, ... }:
let
inherit (lib) types mkOption;
in
{
options = {
failure_threshold = mkOption {
type = types.ints.unsigned;
default = 3;
example = 3;
};
http_get = mkOption {
type = types.nullOr (types.submodule {
options = {
host = mkOption {
type = types.str;
example = "google.com";
};
scheme = mkOption {
type = types.str;
default = "http";
example = "http";
};
path = mkOption {
type = types.str;
default = "/";
example = "/";
};
port = mkOption {
type = types.port;
example = "8080";
};
};
});
default = null;
};
exec = mkOption {
type = types.nullOr (types.submodule {
command = mkOption {
type = types.str;
example = "ps -ef | grep -v grep | grep my-proccess";
};
});
default = null;
};
initial_delay_seconds = mkOption {
type = types.ints.unsigned;
default = 0;
example = 0;
};
period_seconds = mkOption {
type = types.ints.unsigned;
default = 10;
example = 10;
};
success_threshold = mkOption {
type = types.ints.unsigned;
default = 1;
example = 1;
};
timeout_seconds = mkOption {
type = types.ints.unsigned;
default = 3;
example = 3;
};
};
}

View File

@ -0,0 +1,117 @@
{ lib, ... }:
let
inherit (lib) types mkOption;
probeType = types.submoduleWith {
specialArgs = { inherit lib; };
modules = [ ./probe.nix ];
};
in
{
options = {
command = import ./command.nix { inherit lib; } {
description = ''
The command that runs this process
If a package is given, its executable is used as the command. This is
useful to pass in a `writeShellApplication.`
'';
};
depends_on = mkOption {
description = "Process dependency relationships";
type = types.nullOr (types.attrsOf (types.submodule {
options = {
condition = mkOption {
type = types.enum [
"process_completed"
"process_completed_successfully"
"process_healthy"
"process_started"
];
example = "process_healthy";
};
};
}));
default = null;
};
availability = {
restart = mkOption {
type = types.nullOr (types.enum [
"always"
"on_failure"
"exit_on_failure"
"no"
]);
default = null;
example = "on_failure";
};
backoff_seconds = mkOption {
type = types.nullOr types.ints.unsigned;
default = null;
example = 2;
};
max_restarts = mkOption {
type = types.nullOr types.ints.unsigned;
default = null;
example = 0;
};
};
shutdown = {
command = mkOption {
type = types.nullOr types.str;
default = null;
example = "sleep 2 && pkill -f 'test_loop.bash my-proccess'";
};
signal = mkOption {
type = types.nullOr types.ints.unsigned;
default = null;
example = 15;
};
timeout_seconds = mkOption {
type = types.nullOr types.ints.unsigned;
default = null;
example = 10;
};
};
working_dir = mkOption {
type = types.nullOr types.str;
default = null;
example = "/tmp";
};
readiness_probe = mkOption {
type = types.nullOr probeType;
default = null;
};
liveness_probe = mkOption {
type = types.nullOr probeType;
default = null;
};
environment = import ./environment.nix { inherit lib; };
log_location = mkOption {
type = types.nullOr types.str;
default = null;
example = "./pc.my-proccess.log";
};
disable_ansi_colors = mkOption {
type = types.nullOr types.bool;
default = null;
example = true;
};
is_daemon = mkOption {
type = types.nullOr types.bool;
default = null;
example = true;
};
disabled = mkOption {
type = types.nullOr types.bool;
default = null;
example = true;
};
};
}

View File

@ -15,10 +15,13 @@
perSystem = { pkgs, lib, ... }: {
# This adds a `self.packages.default`
process-compose."default" = {
debug = true;
tui = false;
settings = {
environment = [
"DATAFILE=data.sqlite"
];
processes = {
# Create a simple sqlite db
sqlite-init.command =
let
@ -30,21 +33,28 @@
'';
};
in
''
echo "`date`: Creating database..."
${lib.getExe pkgs.sqlite} data.sqlite < ${sqlFile}
echo "`date`: Done."
'';
pkgs.writeShellApplication {
name = "sqlite-init";
text = ''
echo "$(date): Creating database ($DATAFILE) ..."
${lib.getExe pkgs.sqlite} "$DATAFILE" < ${sqlFile}
echo "$(date): Done."
'';
};
# Query something, write to result.txt
sqlite-query = {
command = ''
${lib.getExe pkgs.sqlite} data.sqlite \
'select val from demo where val = "Hello"' \
> result.txt
'';
command = pkgs.writeShellApplication {
name = "sqlite-query";
text = ''
${lib.getExe pkgs.sqlite} "$DATAFILE" \
'select val from demo where val = "Hello"' \
> result.txt
'';
};
# The 'depends_on' will have this process wait until the above one is completed.
depends_on."sqlite-init".condition = "process_completed_successfully";
availability.restart = "no";
};
};
};

View File

@ -6,6 +6,6 @@ cd "$(dirname "$0")"
(cd ../example && nix build -L --override-input process-compose-flake ..)
# Then run the test.
rm -f ./data.sqlite
rm -f ./data.sqlite ./result.txt
nix run -L --override-input process-compose-flake .. .
[[ $(cat result.txt) == "Hello" ]] && echo "Test passed" || (echo "Test failed" && exit 1)