From 0c592d81f38ce2ba0bf903a126fc4b99c1ea5abe Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Wed, 2 Jul 2025 03:39:36 +0200 Subject: [PATCH] WIP: (broken) implement test --- deployment/data-model-test.nix | 112 ++++++++++++++++++++++++++++----- deployment/data-model.nix | 45 ++++++++----- deployment/function.nix | 11 +++- 3 files changed, 136 insertions(+), 32 deletions(-) diff --git a/deployment/data-model-test.nix b/deployment/data-model-test.nix index 76b643b2..2817a2c4 100644 --- a/deployment/data-model-test.nix +++ b/deployment/data-model-test.nix @@ -23,19 +23,98 @@ in { config, ... }: { config = { - resources.nixos = { - # TODO: consumer = ? - # TODO: provider = ? - }; - applications.hello = { - description = ''Command-line tool that will print "Hello, world!" on the terminal''; - # TODO: module = ? - # TODO: config=mapping = ? - }; - environments.single-nixos-vm = { - # TODO: resources = ? - # TODO: resource-mapping = ? + resources.login-shell = { + description = "The operator needs to be able to log into the shell"; + request = + { ... }: + { + _class = "fediversity-resource-request"; + options = { + wheel = mkOption { + description = "Whether the login user needs root permissions"; + type = types.bool; + default = false; + }; + packages = mkOption { + description = "Packages that need to be available in the user environment"; + type = with types; attrsOf package; + }; + }; + }; + policy = + { config, ... }: + { + _class = "fediversity-resource-policy"; + options = { + username = mkOption { + description = "Username for the operator"; + type = types.str; # TODO: use the proper constraints from NixOS + }; + wheel = mkOption { + description = "Whether to allow login with root permissions"; + type = types.bool; + default = false; + }; + apply = mkOption { + type = with types; functionTo raw; # TODO: splice out the user type from NixOS + default = + requests: + let + # Filter out requests that need wheel if policy doesn't allow it + validRequests = lib.filterAttrs (name: req: !req.wheel || config.wheel) requests; + in + lib.optionalAttrs (validRequests != { }) { + ${config.username} = { + isNormalUser = true; + packages = with lib; concatMapAttrs (name: request: attrValues request.packages) validRequests; + extraGroups = lib.optional config.wheel "wheel"; + }; + }; + }; + }; + }; }; + applications.hello = + { ... }: + { + description = ''Command-line tool that will print "Hello, world!" on the terminal''; + module = + { ... }: + { + enable = lib.mkEnableOption "Hello in the shell"; + }; + implementation = + cfg: + lib.optionalAttrs cfg.enable { + dummy.login-shell.packages.hello = pkgs.hello; + }; + }; + environments.single-nixos-vm = + { config, ... }: + { + resources.shell.login-shell.username = "operator"; + 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 = + { pkgs, ... }: + { + users.users = config.resources.shell.login-shell.apply ( + lib.filterAttrs (name: value: value ? login-shell) requests + ); + }; + }; + }; + }; }; options = { example-configuration = mkOption { @@ -47,7 +126,7 @@ in }; }; example-deployment = mkOption { - type = nixops4Deployment; + type = types.submodule nixops4Deployment; readOnly = true; default = config.environments.single-nixos-vm.deployment config.example-configuration; }; @@ -56,10 +135,9 @@ in ); in { - - }; - expected = - { + inherit (fediversity) example-deployment; }; + expected = { + }; }; } diff --git a/deployment/data-model.nix b/deployment/data-model.nix index 9f2a7e9f..305a25d9 100644 --- a/deployment/data-model.nix +++ b/deployment/data-model.nix @@ -19,10 +19,11 @@ let functionType = import ./function.nix; application-resources = { options.resources = mkOption { + # TODO: maybe transpose, and group the resources by type instead type = attrsOf ( attrTag ( lib.mapAttrs' (name: resource: { - ${name} = mkOption { type = resource.consumer; }; + ${name} = mkOption { type = resource.request; }; }) config.resources ) ); @@ -33,20 +34,33 @@ in { options = { resources = mkOption { - description = "Collection of deployment resources that can be configured by hosting providers and required by applications"; + description = "Collection of deployment resources that can be required by applications and policed by hosting providers"; type = attrsOf ( submodule ( { config, ... }: { _class = "fediversity-resource"; options = { - consumer = mkOption { - description = "Options for declaring resource requirements by an application, a description of how the resource is consumed"; - type = deferredModuleWith { staticModules = [ { _class = "fediversity-resource-consumer"; } ]; }; + description = mkOption { + description = "Description of the resource to help application module authors and hosting providers to work with it"; + type = types.str; }; - provider = mkOption { - description = "Options for configuring the resource for the hosting provider, a description of how the resource is made available"; - type = deferredModuleWith { staticModules = [ { _class = "fediversity-resource-provider"; } ]; }; + request = mkOption { + description = "Options for declaring resource requirements by an application, a description of how the resource is consumed or accessed"; + type = deferredModuleWith { staticModules = [ { _class = "fediversity-resource-request"; } ]; }; + }; + policy = mkOption { + description = "Options for configuring the resource policy for the hosting provider, a description of how the resource is made available"; + type = deferredModuleWith { + staticModules = [ + { + _class = "fediversity-resource-policy"; + options.apply = mkOption { + desciption = "Apply the policy to a request"; + }; + } + ]; + }; }; }; } @@ -97,11 +111,16 @@ in _class = "fediversity-environment"; options = { resources = mkOption { - description = "Resources made available by the hosting provider"; + description = '' + Resources made available by the hosting provider, and their policies. + + Setting this is optional, but provides a place to declare that information for programmatic use in the resource mapping. + ''; + # TODO: maybe transpose, and group the resources by type instead type = attrsOf ( attrTag ( lib.mapAttrs' (name: resource: { - ${name} = mkOption { type = resource.provider; }; + ${name} = mkOption { type = resource.policy; }; }) config.resources ) ); @@ -144,10 +163,8 @@ in readOnly = true; default = submodule (configuration: { options = { - enable = mkOption { - description = "Whether to enable the configuration"; - type = types.bool; - default = false; + enable = lib.mkEnableOption { + description = "your Fediversity configuration"; }; applications = lib.mapAttrs ( name: application: diff --git a/deployment/function.nix b/deployment/function.nix index b6a208f0..4f473107 100644 --- a/deployment/function.nix +++ b/deployment/function.nix @@ -1,7 +1,16 @@ /** Modular function type */ -{ config, ... }: +{ config, lib, ... }: +let + inherit (lib) mkOption types; + inherit (types) + deferredModule + submodule + functionTo + optionType + ; +in { options = { input-type = mkOption {