{ pkgs, lib, config, inputs, sources ? import ../npins, ... }: let inherit (lib) attrValues concatMapAttrs filterAttrs hasAttr mapAttrs mapAttrs' mkOption types ; inherit (lib.types) attrTag attrsOf deferredModuleWith functionTo optionType submodule ; functionType = submodule ./function.nix; # TODO: maybe transpose, and group the resources by type instead application-resources = attrsOf ( attrTag ( lib.mapAttrs (_name: resource: mkOption { type = submodule resource.request; }) config.resources ) ); deployment-type = attrTag (pkgs.callPackage ./run { inherit inputs sources; }); env-output = submodule { options = { deployments = mkOption { type = deployment-type; }; ancilliaryRequests = mkOption { type = submodule { options = { garage = mkOption { type = types.listOf types.unspecified; }; }; }; default = { }; }; }; }; in { options = { deployment-type = mkOption { default = deployment-type; }; env-output = mkOption { default = env-output; }; resources = mkOption { description = "Collection of deployment resources that can be required by applications and policed by hosting providers"; type = attrsOf ( submodule (resource: { _class = "fediversity-resource"; options = { description = mkOption { description = "Description of the resource to help application module authors and hosting providers to work with it"; type = types.str; }; 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"; } ]; }; default = { }; }; 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 = [ (policy: { _class = "fediversity-resource-policy"; options = { resource-type = mkOption { description = "The type of resource this policy configures"; type = types.optionType; default = types.unspecified; }; # FIXME make function type explicit: `listOf resource.config.request -> resource-type` # and then also rename this to be consistent with the application's resource mapping apply = mkOption { description = "Apply the policy to a request"; type = functionTo policy.config.resource-type; }; process = mkOption { # FIXME make function type explicit: `attrsOf application-resources -> resource-type` type = functionTo policy.config.resource-type; default = requests: let resourceName = resource.config._module.args.name; relevantRequests = mapAttrs (_: filterAttrs (_: hasAttr resourceName)) requests; flatRequests = concatMapAttrs ( app: mapAttrs' ( k: request: { name = "${app}.${k}"; value = request.${resourceName}; } ) ) relevantRequests; requestList = attrValues flatRequests; in policy.config.apply requestList; }; }; }) ]; }; }; }; }) ); }; applications = mkOption { description = "Collection of Fediversity applications"; type = attrsOf ( submodule (application: { _class = "fediversity-application"; options = { description = mkOption { description = "Description to be shown in the application overview"; type = types.str; }; module = mkOption { 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; 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 = functionType; default = { input-type = submodule application.config.module; output-type = application-resources; implementation = application.config.implementation; }; }; }; }) ); }; environments = mkOption { description = "Run-time environments for Fediversity applications to be deployed to"; type = attrsOf ( submodule (environment: { _class = "fediversity-environment"; options = { resources = mkOption { 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: mkOption { type = submodule resource.policy; }) config.resources ) ); default = { }; }; 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 deployment"; type = functionType; default = { input-type = submodule { options = { deployment-name = mkOption { type = types.listOf types.str; default = [ "default" ]; }; required-resources = mkOption { type = attrsOf application-resources; }; }; }; output-type = env-output; implementation = environment.config.implementation; }; }; config-mapping = mkOption { description = "Mapping from a configuration to a deployment"; type = functionType; default = { input-type = submodule { options = { deployment-name = mkOption { type = types.listOf types.str; default = [ "default" ]; }; configuration = mkOption { type = config.configuration; }; }; }; output-type = env-output; implementation = { deployment-name, configuration, }: # TODO: check cfg.enable.true let required-resources = lib.mapAttrs ( name: application-settings: config.applications.${name}.resources application-settings ) configuration.applications; in environment.config.resource-mapping.apply { inherit required-resources deployment-name; }; }; }; # 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; default = environment.config.config-mapping.apply; }; }; }) ); }; configuration = mkOption { description = "Configuration type declaring options to be set by operators"; type = optionType; default = submodule { options = { enable = lib.mkEnableOption "your Fediversity configuration"; applications = lib.mapAttrs ( _name: application: mkOption { description = application.description; type = submodule application.module; default = { }; } ) config.applications; domain = mkOption { type = types.enum [ "fediversity.net" ]; description = '' Apex domain under which the services will be deployed. ''; default = "fediversity.net"; }; ## NOTE: In practice, we will want to plug our services to a central ## authentication service, eg. LDAP. In the meantime, for the demo ## effect (and for testing, tbh), we need a way to inject an initial ## user into our services. initialUser = mkOption { description = '' Some services require an initial user to access them. This option sets the credentials for such an initial user. ''; type = with types; nullOr (submodule { options = { username = mkOption { type = str; description = "Username for login"; }; displayName = mkOption { type = str; description = "Display name of the user"; }; email = mkOption { type = str; description = "User's email address"; }; password = mkOption { type = str; description = "Password for login"; }; }; }); default = null; }; }; }; }; }; }