From 6fa9c40662ef8db89c11bd0a80e5faf2a3a9f7cc Mon Sep 17 00:00:00 2001 From: Kiara Grouwstra Date: Fri, 1 Aug 2025 17:56:35 +0200 Subject: [PATCH 1/4] factor out function wrapper to module function note that this does not yet contain the 'type check', as this does not function yet. this could be added in `data-model.nix` like: ```nix function-type-check = mkOption { type = application.config.config-mapping.function-type; readOnly = true; default = input: { inherit input; output = application.config.config-mapping.implementation-type input; }; }; ``` ... or even to `function.nix` itself, like: ```nix function-type-check = mkOption { type = config.function-type; readOnly = true; default = input: { inherit input; output = config.implementation-type input; }; }; ``` --- deployment/data-model-test.nix | 49 ++++++++++++++++------------------ deployment/data-model.nix | 8 +++--- deployment/function.nix | 5 ++++ 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/deployment/data-model-test.nix b/deployment/data-model-test.nix index 24d5cd6c..7ce2fce2 100644 --- a/deployment/data-model-test.nix +++ b/deployment/data-model-test.nix @@ -99,40 +99,37 @@ in { options.enable = lib.mkEnableOption "Hello in the shell"; }; - implementation = cfg: { - input = cfg; - output = lib.optionalAttrs cfg.enable { + implementation = + cfg: + lib.optionalAttrs cfg.enable { resources.hello.login-shell.packages.hello = pkgs.hello; }; - }; }; environments.single-nixos-vm = { config, ... }: { resources.operator-environment.login-shell.username = "operator"; - implementation = requests: { - input = requests; - output = - { providers, ... }: - { - providers = { - inherit (inputs.nixops4.modules.nixops4Provider) local; - }; - resources.the-machine = { - type = providers.local.exec; - imports = [ - inputs.nixops4-nixos.modules.nixops4Resource.nixos - ]; - nixos.module = - { ... }: - { - users.users = config.resources.shell.login-shell.apply ( - lib.filterAttrs (_name: value: value ? login-shell) requests - ); - }; - }; + implementation = + requests: + { providers, ... }: + { + providers = { + inherit (inputs.nixops4.modules.nixops4Provider) local; }; - }; + resources.the-machine = { + type = providers.local.exec; + imports = [ + inputs.nixops4-nixos.modules.nixops4Resource.nixos + ]; + nixos.module = + { ... }: + { + users.users = config.resources.shell.login-shell.apply ( + lib.filterAttrs (_name: value: value ? login-shell) requests + ); + }; + }; + }; }; }; options = { diff --git a/deployment/data-model.nix b/deployment/data-model.nix index c3d5d53a..cefa1e4f 100644 --- a/deployment/data-model.nix +++ b/deployment/data-model.nix @@ -99,13 +99,13 @@ in }; 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; + type = application.config.config-mapping.implementation-type; }; resources = mkOption { description = "Compute resources required by an application"; type = functionTo application.config.config-mapping.output-type; readOnly = true; - default = input: (application.config.implementation input).output; + default = application.config.implementation; }; # TODO(@fricklerhandwerk): this needs a better name, it's just the type config-mapping = mkOption { @@ -142,7 +142,7 @@ 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; + type = environment.config.resource-mapping.implementation-type; }; resource-mapping = mkOption { description = "Function type for the mapping from resources to a (NixOps4) deployment"; @@ -168,7 +168,7 @@ in name: application-settings: config.applications.${name}.resources application-settings ) cfg.applications; in - (environment.config.implementation required-resources).output; + environment.config.implementation required-resources; }; }; diff --git a/deployment/function.nix b/deployment/function.nix index f0210a34..48ab9abe 100644 --- a/deployment/function.nix +++ b/deployment/function.nix @@ -18,6 +18,11 @@ in output-type = mkOption { type = optionType; }; + implementation-type = mkOption { + type = optionType; + readOnly = true; + default = functionTo config.output-type; + }; function-type = mkOption { type = optionType; readOnly = true; -- 2.48.1 From fb004a4d4c69893d991e71908af0381ce46504e9 Mon Sep 17 00:00:00 2001 From: Kiara Grouwstra Date: Sun, 17 Aug 2025 17:24:43 +0200 Subject: [PATCH 2/4] combine UX with parameter type-check - DRYing TODO --- deployment/data-model-test.nix | 6 +- deployment/data-model.nix | 137 ++++++++++++++++++++++++++------- deployment/function.nix | 41 ---------- 3 files changed, 112 insertions(+), 72 deletions(-) delete mode 100644 deployment/function.nix diff --git a/deployment/data-model-test.nix b/deployment/data-model-test.nix index 7ce2fce2..5bd58b21 100644 --- a/deployment/data-model-test.nix +++ b/deployment/data-model-test.nix @@ -99,7 +99,7 @@ in { options.enable = lib.mkEnableOption "Hello in the shell"; }; - implementation = + config-mapping.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"; - implementation = + resource-mapping.implementation = requests: { providers, ... }: { @@ -149,7 +149,7 @@ in }; } ); - resources = fediversity.applications.hello.resources fediversity.example-configuration.applications.hello; + resources = fediversity.applications.hello.config-mapping.apply 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 { diff --git a/deployment/data-model.nix b/deployment/data-model.nix index cefa1e4f..9620e3ea 100644 --- a/deployment/data-model.nix +++ b/deployment/data-model.nix @@ -15,7 +15,6 @@ let functionTo ; - functionType = import ./function.nix; application-resources = submodule { options.resources = mkOption { # TODO: maybe transpose, and group the resources by type instead @@ -97,25 +96,63 @@ 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.implementation-type; - }; - resources = mkOption { - description = "Compute resources required by an application"; - type = functionTo application.config.config-mapping.output-type; - readOnly = true; - default = application.config.implementation; - }; - # TODO(@fricklerhandwerk): this needs a better name, it's just the type + # TODO(@fricklerhandwerk): this needs a better name config-mapping = mkOption { description = "Function type for the mapping from application configuration to required resources"; - type = submodule functionType; - readOnly = true; - default = { - input-type = submodule application.config.module; - output-type = application-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; + 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; + 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 = { }; }; }; }) @@ -140,18 +177,62 @@ in ) ); }; - implementation = mkOption { - description = "Mapping of resources required by applications to available resources; the result can be deployed"; - type = environment.config.resource-mapping.implementation-type; - }; resource-mapping = mkOption { description = "Function type for the mapping from resources to a (NixOps4) deployment"; - type = submodule functionType; - readOnly = true; - default = { - input-type = application-resources; - output-type = nixops4Deployment; - }; + 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; + 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; + 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. diff --git a/deployment/function.nix b/deployment/function.nix deleted file mode 100644 index 48ab9abe..00000000 --- a/deployment/function.nix +++ /dev/null @@ -1,41 +0,0 @@ -/** - 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; - }; - implementation-type = mkOption { - type = optionType; - readOnly = true; - default = functionTo config.output-type; - }; - function-type = mkOption { - type = optionType; - readOnly = true; - default = functionTo (submodule { - options = { - input = mkOption { - type = config.input-type; - }; - output = mkOption { - type = config.output-type; - }; - }; - }); - }; - }; -} -- 2.48.1 From efbe7deddd7ed60abfd6e978ca9bace0cf2c283d Mon Sep 17 00:00:00 2001 From: Kiara Grouwstra Date: Sun, 17 Aug 2025 23:17:55 +0200 Subject: [PATCH 3/4] 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! --- deployment/data-model-test.nix | 6 +- deployment/data-model.nix | 170 ++++++++++----------------------- deployment/function.nix | 58 +++++++++++ 3 files changed, 111 insertions(+), 123 deletions(-) create mode 100644 deployment/function.nix diff --git a/deployment/data-model-test.nix b/deployment/data-model-test.nix index 5bd58b21..7ce2fce2 100644 --- a/deployment/data-model-test.nix +++ b/deployment/data-model-test.nix @@ -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 { diff --git a/deployment/data-model.nix b/deployment/data-model.nix index 9620e3ea..c6fcfbd4 100644 --- a/deployment/data-model.nix +++ b/deployment/data-model.nix @@ -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; - 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; - 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 = { }; + description = "Compute resources required by an application"; + type = submodule functionType; + readOnly = true; + default = { + input-type = submodule application.config.module; + output-type = application-resources; + implementation = application.config.implementation; + }; }; }; }) @@ -177,80 +141,46 @@ 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; - 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; - 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 = { }; + type = submodule functionType; + readOnly = true; + default = { + input-type = application-resources; + output-type = nixops4Deployment; + implementation = environment.config.implementation; + }; + }; + config-mapping = mkOption { + description = "Mapping from a configuration to a deployment"; + type = submodule functionType; + readOnly = true; + default = { + input-type = config.configuration; + output-type = nixops4Deployment; + implementation = + cfg: + # TODO: check cfg.enable.true + let + required-resources = lib.mapAttrs ( + name: application-settings: config.applications.${name}.resources application-settings + ) cfg.applications; + in + 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 = functionTo (environment.config.resource-mapping.output-type); + type = environment.config.config-mapping.function-type; readOnly = true; - default = - cfg: - # TODO: check cfg.enable.true - let - required-resources = lib.mapAttrs ( - name: application-settings: config.applications.${name}.resources application-settings - ) cfg.applications; - in - environment.config.implementation required-resources; - + default = environment.config.config-mapping.apply; }; }; }) diff --git a/deployment/function.nix b/deployment/function.nix new file mode 100644 index 00000000..76795e68 --- /dev/null +++ b/deployment/function.nix @@ -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; + }; + }; +} -- 2.48.1 From d066b1d55c601b339330bde6f9b066c0144288f4 Mon Sep 17 00:00:00 2001 From: Kiara Grouwstra Date: Mon, 18 Aug 2025 10:24:09 +0200 Subject: [PATCH 4/4] rename internal `implementation` to `declare`, mirroring `apply` --- deployment/data-model.nix | 6 +++--- deployment/function.nix | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/deployment/data-model.nix b/deployment/data-model.nix index c6fcfbd4..acd52b66 100644 --- a/deployment/data-model.nix +++ b/deployment/data-model.nix @@ -115,7 +115,7 @@ in default = { input-type = submodule application.config.module; output-type = application-resources; - implementation = application.config.implementation; + declare = application.config.implementation; }; }; }; @@ -152,7 +152,7 @@ in default = { input-type = application-resources; output-type = nixops4Deployment; - implementation = environment.config.implementation; + declare = environment.config.implementation; }; }; config-mapping = mkOption { @@ -162,7 +162,7 @@ in default = { input-type = config.configuration; output-type = nixops4Deployment; - implementation = + declare = cfg: # TODO: check cfg.enable.true let diff --git a/deployment/function.nix b/deployment/function.nix index 76795e68..3859cd7a 100644 --- a/deployment/function.nix +++ b/deployment/function.nix @@ -37,7 +37,7 @@ in }; }); }; - implementation = mkOption { + declare = mkOption { type = config.function-type; default = _: { }; }; -- 2.48.1