factor common logic back out

note that i worked around the question of how to delegate part of the
options to the consumer by creating aliases for different parts of the
function slot filling.
i've been pretty arbitrary about this so far tho, and mostly to preserve
existing interfaces, rather than out of conscious UX design per se.
so the interface can def change still, but at least the _user_'s side is
more DRY now!
This commit is contained in:
Kiara Grouwstra 2025-08-17 23:17:55 +02:00
parent fb004a4d4c
commit efbe7deddd
Signed by: kiara
SSH key fingerprint: SHA256:COspvLoLJ5WC5rFb9ZDe5urVCkK4LJZOsjfF4duRJFU
3 changed files with 111 additions and 123 deletions

View file

@ -99,7 +99,7 @@ in
{
options.enable = lib.mkEnableOption "Hello in the shell";
};
config-mapping.implementation =
implementation =
cfg:
lib.optionalAttrs cfg.enable {
resources.hello.login-shell.packages.hello = pkgs.hello;
@ -109,7 +109,7 @@ in
{ config, ... }:
{
resources.operator-environment.login-shell.username = "operator";
resource-mapping.implementation =
implementation =
requests:
{ providers, ... }:
{
@ -149,7 +149,7 @@ in
};
}
);
resources = fediversity.applications.hello.config-mapping.apply fediversity.example-configuration.applications.hello;
resources = fediversity.applications.hello.resources fediversity.example-configuration.applications.hello;
hello-shell = resources.resources.hello.login-shell;
environment = fediversity.environments.single-nixos-vm.resources.operator-environment.login-shell;
result = mkDeployment {

View file

@ -15,6 +15,7 @@ let
functionTo
;
functionType = import ./function.nix;
application-resources = submodule {
options.resources = mkOption {
# TODO: maybe transpose, and group the resources by type instead
@ -96,63 +97,26 @@ in
description = "Operator-facing configuration options for the application";
type = deferredModuleWith { staticModules = [ { _class = "fediversity-application-config"; } ]; };
};
implementation = mkOption {
description = "Mapping of application configuration to deployment resources, a description of what an application needs to run";
type = application.config.config-mapping.function-type;
};
resources = mkOption {
description = "Compute resources required by an application";
type = application.config.config-mapping.function-type;
readOnly = true;
default = application.config.config-mapping.apply;
};
# TODO(@fricklerhandwerk): this needs a better name
config-mapping = mkOption {
description = "Function type for the mapping from application configuration to required resources";
type = submodule (
{ config, ... }:
{
options = {
input-type = mkOption {
type = optionType;
default = submodule application.config.module;
# default = types.int;
};
output-type = mkOption {
type = optionType;
default = application-resources;
};
function-type = mkOption {
type = optionType;
description = "Compute resources required by an application";
type = submodule functionType;
readOnly = true;
default = functionTo config.output-type;
default = {
input-type = submodule application.config.module;
output-type = application-resources;
implementation = application.config.implementation;
};
wrapper-type = mkOption {
type = optionType;
readOnly = true;
default = functionTo (submodule {
options = {
input = mkOption {
type = config.input-type;
};
output = mkOption {
type = config.output-type;
};
};
});
};
implementation = mkOption {
type = config.function-type;
description = "Mapping of application configuration to deployment resources, a description of what an application needs to run";
default = _: { };
};
wrapper = mkOption {
type = config.wrapper-type;
readOnly = true;
default = input: fn: {
inherit input;
output = config.implementation fn.config.input;
};
};
apply = mkOption {
type = config.function-type;
readOnly = true;
default = input: (config.wrapper input).output;
};
};
}
);
default = { };
};
};
})
@ -177,71 +141,28 @@ in
)
);
};
implementation = mkOption {
description = "Mapping of resources required by applications to available resources; the result can be deployed";
type = environment.config.resource-mapping.function-type;
};
resource-mapping = mkOption {
description = "Function type for the mapping from resources to a (NixOps4) deployment";
type = submodule (
{ config, ... }:
{
options = {
input-type = mkOption {
type = optionType;
default = submodule application-resources;
# default = types.int;
};
output-type = mkOption {
type = optionType;
default = nixops4Deployment;
};
function-type = mkOption {
type = optionType;
type = submodule functionType;
readOnly = true;
default = functionTo config.output-type;
default = {
input-type = application-resources;
output-type = nixops4Deployment;
implementation = environment.config.implementation;
};
wrapper-type = mkOption {
type = optionType;
};
config-mapping = mkOption {
description = "Mapping from a configuration to a deployment";
type = submodule functionType;
readOnly = true;
default = functionTo (submodule {
options = {
input = mkOption {
type = config.input-type;
};
output = mkOption {
type = config.output-type;
};
};
});
};
implementation = mkOption {
type = config.function-type;
description = "Mapping of resources required by applications to available resources; the result can be deployed";
default = _: { };
};
wrapper = mkOption {
type = config.wrapper-type;
readOnly = true;
default = input: fn: {
inherit input;
output = config.implementation fn.config.input;
};
};
apply = mkOption {
type = config.function-type;
readOnly = true;
default = input: (config.wrapper input).output;
};
};
}
);
default = { };
};
# TODO(@fricklerhandwerk): maybe this should be a separate thing such as `fediversity-setup`,
# which makes explicit which applications and environments are available.
# then the deployments can simply be the result of the function application baked into this module.
deployment = mkOption {
description = "Generate a deployment from a configuration, by applying an environment's resource policies to the applications' resource mappings";
type = functionTo (environment.config.resource-mapping.output-type);
readOnly = true;
default =
default = {
input-type = config.configuration;
output-type = nixops4Deployment;
implementation =
cfg:
# TODO: check cfg.enable.true
let
@ -249,8 +170,17 @@ in
name: application-settings: config.applications.${name}.resources application-settings
) cfg.applications;
in
environment.config.implementation required-resources;
environment.config.resource-mapping.apply required-resources;
};
};
# TODO(@fricklerhandwerk): maybe this should be a separate thing such as `fediversity-setup`,
# which makes explicit which applications and environments are available.
# then the deployments can simply be the result of the function application baked into this module.
deployment = mkOption {
description = "Generate a deployment from a configuration, by applying an environment's resource policies to the applications' resource mappings";
type = environment.config.config-mapping.function-type;
readOnly = true;
default = environment.config.config-mapping.apply;
};
};
})

58
deployment/function.nix Normal file
View file

@ -0,0 +1,58 @@
/**
Modular function type
*/
{ config, lib, ... }:
let
inherit (lib) mkOption types;
inherit (types)
submodule
functionTo
optionType
;
in
{
options = {
input-type = mkOption {
type = optionType;
};
output-type = mkOption {
type = optionType;
};
function-type = mkOption {
type = optionType;
readOnly = true;
default = functionTo config.output-type;
};
wrapper-type = mkOption {
type = optionType;
readOnly = true;
default = functionTo (submodule {
options = {
input = mkOption {
type = config.input-type;
};
output = mkOption {
type = config.output-type;
};
};
});
};
implementation = mkOption {
type = config.function-type;
default = _: { };
};
wrapper = mkOption {
type = config.wrapper-type;
readOnly = true;
default = input: fn: {
inherit input;
output = config.implementation fn.config.input;
};
};
apply = mkOption {
type = config.function-type;
readOnly = true;
default = input: (config.wrapper input).output;
};
};
}