let inherit (import ../default.nix { }) pkgs inputs; inherit (pkgs) lib; inherit (lib) mkOption types; eval = module: (lib.evalModules { specialArgs = { inherit inputs; }; modules = [ module ./data-model.nix ]; }).config; nixops4Deployment = inputs.nixops4.modules.nixops4Deployment.default; inherit (inputs.nixops4.lib) mkDeployment; in { _class = "nix-unit"; test-eval = { /** This tests a very simple arrangement that features all ingredients of the Fediversity business logic: application, resource, environment, deployment; and wires it all up in one end-to-end exercise. - The dummy resource is a login shell made available for some user. - The dummy application is `hello` that requires a shell to be deployed. - The dummy environment is a single NixOS VM that hosts one login shell, for the operator. - The dummy configuration enables the `hello` application. This will produce a NixOps4 deployment for a NixOS VM with a login shell for the operator and `hello` available. */ expr = let fediversity = eval ( { config, ... }: { config = { 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; }; }; config = { resource-type = types.raw; # TODO: splice out the user type from NixOS apply = requests: let # Filter out requests that need wheel if policy doesn't allow it validRequests = lib.filterAttrs ( _name: req: !req.login-shell.wheel || config.wheel ) requests.resources; in lib.optionalAttrs (validRequests != { }) { ${config.username} = { isNormalUser = true; packages = with lib; attrValues (concatMapAttrs (_name: request: request.login-shell.packages) validRequests); extraGroups = lib.optional config.wheel "wheel"; }; }; }; }; }; applications.hello = { ... }: { description = ''Command-line tool that will print "Hello, world!" on the terminal''; module = { ... }: { options.enable = lib.mkEnableOption "Hello in the shell"; }; implementation = cfg: { input = cfg; output = 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.operator-environment.login-shell.apply ( lib.filterAttrs (_name: value: value ? login-shell) requests ); }; }; }; }; }; }; options = { example-configuration = mkOption { type = config.configuration; readOnly = true; default = { enable = true; applications.hello.enable = true; }; }; example-deployment = mkOption { type = types.submodule nixops4Deployment; readOnly = true; default = config.environments.single-nixos-vm.deployment config.example-configuration; }; }; } ); 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 { modules = [ (fediversity.environments.single-nixos-vm.deployment fediversity.example-configuration) ]; }; in { number-of-resources = with lib; length (attrNames fediversity.resources); inherit (fediversity) example-configuration; hello-package-exists = hello-shell.packages ? hello; wheel-required = hello-shell.wheel; wheel-allowed = environment.wheel; operator-shell = let operator = (environment.apply resources).operator; in { inherit (operator) isNormalUser; packages = map (p: "${p.pname}") operator.packages; extraGroups = operator.extraGroups; }; deployment = { inherit (result) _type; deploymentFunction = lib.isFunction result.deploymentFunction; getProviders = lib.isFunction result.getProviders; }; }; expected = { number-of-resources = 1; example-configuration = { enable = true; applications.hello.enable = true; }; hello-package-exists = true; wheel-required = false; wheel-allowed = false; operator-shell = { isNormalUser = true; packages = [ "hello" ]; extraGroups = [ ]; }; deployment = { _type = "nixops4Deployment"; deploymentFunction = true; getProviders = true; }; }; }; }