forked from fediversity/fediversity
		
	
		
			
				
	
	
		
			839 lines
		
	
	
	
		
			29 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			839 lines
		
	
	
	
		
			29 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
| {
 | |
|   pkgs,
 | |
|   lib,
 | |
|   config,
 | |
|   inputs,
 | |
|   sources ? import ../npins,
 | |
|   ...
 | |
| }:
 | |
| let
 | |
|   inherit (lib) mkOption types;
 | |
|   inherit (lib.types)
 | |
|     attrTag
 | |
|     attrsOf
 | |
|     deferredModuleWith
 | |
|     functionTo
 | |
|     nullOr
 | |
|     optionType
 | |
|     raw
 | |
|     str
 | |
|     submodule
 | |
|     ;
 | |
|   inherit (pkgs.callPackage ./utils.nix { }) toBash withPackages tfApply;
 | |
|   writeConfig =
 | |
|     {
 | |
|       system,
 | |
|       caller,
 | |
|       root-path,
 | |
|       deployment-type,
 | |
|       deployment-name,
 | |
|       args,
 | |
|     }:
 | |
|     # having a `caller` location and (serializable) `args`, we know
 | |
|     # enough to call it again to extract different info elsewhere later.
 | |
|     # we use this to make a deployment script using the desired nixos config,
 | |
|     # which would otherwise not be serializable, while nix also makes it hard to
 | |
|     # produce its derivation to pass thru without a `nix-instantiate` call,
 | |
|     # which in turn would need to be passed the (unserializable) nixos config.
 | |
|     builtins.toString (
 | |
|       pkgs.writers.writeText "configuration.nix" ''
 | |
|         import ${root-path}/deployment/nixos.nix {
 | |
|           system = "${system}";
 | |
|           configuration = (import "${root-path}/${caller}" (builtins.fromJSON "${
 | |
|             lib.replaceStrings [ "\"" ] [ "\\\"" ] (lib.strings.toJSON args)
 | |
|           }")).${deployment-name}.${deployment-type}.nixos-configuration;
 | |
|         }
 | |
|       ''
 | |
|     );
 | |
| 
 | |
|   functionType = submodule ./function.nix;
 | |
|   application-resources = submodule {
 | |
|     options.resources = mkOption {
 | |
|       # TODO: maybe transpose, and group the resources by type instead
 | |
|       type = attrsOf (
 | |
|         attrTag (
 | |
|           lib.mapAttrs (_name: resource: mkOption { type = submodule resource.request; }) config.resources
 | |
|         )
 | |
|       );
 | |
|     };
 | |
|   };
 | |
|   nixops4Deployment = types.deferredModuleWith {
 | |
|     staticModules = [
 | |
|       inputs.nixops4.modules.nixops4Deployment.default
 | |
| 
 | |
|       {
 | |
|         _class = "nixops4Deployment";
 | |
|         _module.args = {
 | |
|           resourceProviderSystem = pkgs.system;
 | |
|           resources = { };
 | |
|         };
 | |
|       }
 | |
|     ];
 | |
|   };
 | |
|   nixos-configuration = mkOption {
 | |
|     description = "A NixOS configuration.";
 | |
|     type = raw;
 | |
|   };
 | |
|   httpBackend = mkOption {
 | |
|     description = "environment variables to configure the TF HTTP back-end, see <https://developer.hashicorp.com/terraform/language/backend/http#configuration-variables>";
 | |
|     type = types.submodule (http-backend: {
 | |
|       options = {
 | |
|         value = mkOption {
 | |
|           readOnly = true;
 | |
|           default = lib.mapAttrs' (k: v: lib.nameValuePair "TF_HTTP_${lib.toUpper k}" (builtins.toString v)) {
 | |
|             inherit (http-backend.config)
 | |
|               address
 | |
|               update_method
 | |
|               lock_address
 | |
|               lock_method
 | |
|               unlock_address
 | |
|               unlock_method
 | |
|               username
 | |
|               password
 | |
|               skip_cert_verification
 | |
|               retry_max
 | |
|               retry_wait_min
 | |
|               retry_wait_max
 | |
|               ;
 | |
|           };
 | |
|         };
 | |
|         address = mkOption {
 | |
|           description = "The address of the REST endpoint";
 | |
|           type = str;
 | |
|         };
 | |
|         update_method = mkOption {
 | |
|           description = "HTTP method to use when updating state.";
 | |
|           type = str;
 | |
|           default = "POST";
 | |
|         };
 | |
|         lock_address = mkOption {
 | |
|           description = "The address of the lock REST endpoint.";
 | |
|           type = str;
 | |
|           default = http-backend.config.address;
 | |
|         };
 | |
|         lock_method = mkOption {
 | |
|           description = "The HTTP method to use when locking.";
 | |
|           type = str;
 | |
|           default = "LOCK";
 | |
|         };
 | |
|         unlock_address = mkOption {
 | |
|           description = "The address of the unlock REST endpoint.";
 | |
|           type = str;
 | |
|           default = http-backend.config.address;
 | |
|         };
 | |
|         unlock_method = mkOption {
 | |
|           description = "The HTTP method to use when unlocking.";
 | |
|           type = str;
 | |
|           default = "UNLOCK";
 | |
|         };
 | |
|         username = mkOption {
 | |
|           description = "The username for HTTP basic authentication.";
 | |
|           type = str;
 | |
|           default = "basic";
 | |
|         };
 | |
|         password = mkOption {
 | |
|           description = "The password for HTTP basic authentication.";
 | |
|           type = str;
 | |
|           default = "fake-secret";
 | |
|         };
 | |
|         skip_cert_verification = mkOption {
 | |
|           description = "Whether to skip TLS verification.";
 | |
|           type = str;
 | |
|           default = "false";
 | |
|         };
 | |
|         retry_max = mkOption {
 | |
|           description = "The number of HTTP request retries.";
 | |
|           type = types.int;
 | |
|           default = 2;
 | |
|         };
 | |
|         retry_wait_min = mkOption {
 | |
|           description = "The minimum time in seconds to wait between HTTP request attempts.";
 | |
|           type = types.int;
 | |
|           default = 1;
 | |
|         };
 | |
|         retry_wait_max = mkOption {
 | |
|           description = "The maximum time in seconds to wait between HTTP request attempts.";
 | |
|           type = types.int;
 | |
|           default = 30;
 | |
|         };
 | |
|       };
 | |
|     });
 | |
|   };
 | |
|   host-ssh = mkOption {
 | |
|     description = "SSH connection info to connect to a single host.";
 | |
|     type = submodule {
 | |
|       options = {
 | |
|         host = mkOption {
 | |
|           description = "the host to access by SSH";
 | |
|           type = str;
 | |
|         };
 | |
|         username = mkOption {
 | |
|           description = "the SSH user to use";
 | |
|           type = nullOr str;
 | |
|           default = null;
 | |
|         };
 | |
|         key-file = mkOption {
 | |
|           description = "path to the user's SSH private key";
 | |
|           type = nullOr str;
 | |
|           example = "/root/.ssh/id_ed25519";
 | |
|         };
 | |
|         sshOpts = mkOption {
 | |
|           description = "Extra SSH options (`-o`) to use.";
 | |
|           type = types.listOf str;
 | |
|           default = [ ];
 | |
|           example = "ConnectTimeout=60";
 | |
|         };
 | |
|       };
 | |
|     };
 | |
|   };
 | |
|   # FIXME allow custom deployment types
 | |
|   # FIXME make deployments environment resources?
 | |
|   deployment-type = attrTag {
 | |
|     ssh-host = mkOption {
 | |
|       description = "A deployment by SSH to update a single existing NixOS host.";
 | |
|       type = submodule (ssh-host: {
 | |
|         options = {
 | |
|           system = mkOption {
 | |
|             description = "The architecture of the system to deploy to.";
 | |
|             type = types.str;
 | |
|           };
 | |
|           inherit nixos-configuration;
 | |
|           ssh = host-ssh;
 | |
|           caller = mkOption {
 | |
|             description = "The calling module to obtain the NixOS configuration from.";
 | |
|             type = types.str;
 | |
|           };
 | |
|           args = mkOption {
 | |
|             description = "The arguments with which to call the module to obtain the NixOS configuration.";
 | |
|             type = types.attrs;
 | |
|           };
 | |
|           deployment-name = mkOption {
 | |
|             description = "The name of the deployment for which to obtain the NixOS configuration.";
 | |
|             type = types.str;
 | |
|             default = "default";
 | |
|           };
 | |
|           root-path = mkOption {
 | |
|             description = "The path to the root of the repository.";
 | |
|             type = types.path;
 | |
|           };
 | |
|           run = mkOption {
 | |
|             type = types.package;
 | |
|             # error: The option `ssh-deployment.ssh-host.run' is read-only, but it's set multiple times.
 | |
|             # readOnly = true;
 | |
|             default =
 | |
|               let
 | |
|                 inherit (ssh-host.config)
 | |
|                   system
 | |
|                   ssh
 | |
|                   caller
 | |
|                   args
 | |
|                   deployment-name
 | |
|                   root-path
 | |
|                   ;
 | |
|                 inherit (ssh)
 | |
|                   host
 | |
|                   username
 | |
|                   key-file
 | |
|                   sshOpts
 | |
|                   ;
 | |
|                 environment = {
 | |
|                   key_file = key-file;
 | |
|                   ssh_opts = sshOpts;
 | |
|                   inherit
 | |
|                     host
 | |
|                     username
 | |
|                     ;
 | |
|                   nixos_conf = writeConfig {
 | |
|                     inherit
 | |
|                       system
 | |
|                       caller
 | |
|                       args
 | |
|                       deployment-name
 | |
|                       root-path
 | |
|                       ;
 | |
|                     deployment-type = "ssh-host";
 | |
|                   };
 | |
|                 };
 | |
|               in
 | |
|               pkgs.writers.writeBashBin "deploy-sh.sh"
 | |
|                 (withPackages [
 | |
|                   pkgs.jq
 | |
|                 ])
 | |
|                 ''
 | |
|                   env ${
 | |
|                     toString (lib.mapAttrsToList (k: v: "${k}=\"${toBash v}\"") environment)
 | |
|                   } bash ./deployment/run/ssh-single-host/run.sh
 | |
|                 '';
 | |
|           };
 | |
|         };
 | |
|       });
 | |
|     };
 | |
|     nixops4 = mkOption {
 | |
|       description = "A NixOps4 NixOS deployment. For an example, see https://github.com/nixops4/nixops4-nixos/blob/main/example/deployment.nix.";
 | |
|       type = nixops4Deployment;
 | |
|     };
 | |
|     tf-host = mkOption {
 | |
|       description = "A Terraform deployment by SSH to update a single existing NixOS host.";
 | |
|       type = submodule (tf-host: {
 | |
|         options = {
 | |
|           system = mkOption {
 | |
|             description = "The architecture of the system to deploy to.";
 | |
|             type = types.str;
 | |
|           };
 | |
|           inherit httpBackend nixos-configuration;
 | |
|           ssh = host-ssh;
 | |
|           caller = mkOption {
 | |
|             description = "The calling module to obtain the NixOS configuration from.";
 | |
|             type = types.str;
 | |
|           };
 | |
|           args = mkOption {
 | |
|             description = "The arguments with which to call the module to obtain the NixOS configuration.";
 | |
|             type = types.attrs;
 | |
|           };
 | |
|           deployment-name = mkOption {
 | |
|             description = "The name of the deployment for which to obtain the NixOS configuration.";
 | |
|             type = types.str;
 | |
|           };
 | |
|           root-path = mkOption {
 | |
|             description = "The path to the root of the repository.";
 | |
|             type = types.path;
 | |
|           };
 | |
|           run = mkOption {
 | |
|             type = types.package;
 | |
|             # error: The option `tf-deployment.tf-host.run' is read-only, but it's set multiple times.
 | |
|             # readOnly = true;
 | |
|             default =
 | |
|               let
 | |
|                 inherit (tf-host.config)
 | |
|                   system
 | |
|                   ssh
 | |
|                   caller
 | |
|                   args
 | |
|                   deployment-name
 | |
|                   root-path
 | |
|                   httpBackend
 | |
|                   ;
 | |
|                 inherit (ssh)
 | |
|                   host
 | |
|                   username
 | |
|                   key-file
 | |
|                   sshOpts
 | |
|                   ;
 | |
|               in
 | |
|               tfApply {
 | |
|                 inherit httpBackend;
 | |
|                 directory = "tf-single-host";
 | |
|                 environment = {
 | |
|                   key_file = key-file;
 | |
|                   ssh_opts = sshOpts;
 | |
|                   inherit
 | |
|                     host
 | |
|                     username
 | |
|                     ;
 | |
|                   nixos_conf = writeConfig {
 | |
|                     inherit
 | |
|                       system
 | |
|                       caller
 | |
|                       args
 | |
|                       deployment-name
 | |
|                       root-path
 | |
|                       ;
 | |
|                     deployment-type = "tf-host";
 | |
|                   };
 | |
|                 };
 | |
|               };
 | |
|           };
 | |
|         };
 | |
|       });
 | |
|     };
 | |
|     tf-proxmox-template = mkOption {
 | |
|       description = ''
 | |
|         A Terraform deployment to upload a virtual machine template to ProxmoX VE.
 | |
|         Proxmox credentials should be set using [environment variables]
 | |
|         (https://registry.terraform.io/providers/bpg/proxmox/latest/docs#environment-variables-summary)
 | |
|         with role `PVEDatastoreAdmin`.
 | |
|       '';
 | |
|       type = submodule (tf-host: {
 | |
|         options = {
 | |
|           system = mkOption {
 | |
|             description = "The architecture of the system to deploy to.";
 | |
|             type = types.str;
 | |
|           };
 | |
|           inherit httpBackend nixos-configuration;
 | |
|           ssh = host-ssh;
 | |
|           node-name = mkOption {
 | |
|             description = "the name of the ProxmoX node to use.";
 | |
|             type = types.str;
 | |
|           };
 | |
|           imageDatastoreId = mkOption {
 | |
|             description = "ID of the datastore of the image.";
 | |
|             type = types.str;
 | |
|             default = "local";
 | |
|           };
 | |
|           run = mkOption {
 | |
|             type = types.package;
 | |
|             # error: The option `tf-deployment.tf-host.run' is read-only, but it's set multiple times.
 | |
|             # readOnly = true;
 | |
|             default =
 | |
|               let
 | |
|                 inherit (tf-host.config)
 | |
|                   system
 | |
|                   ssh
 | |
|                   httpBackend
 | |
|                   node-name
 | |
|                   imageDatastoreId
 | |
|                   ;
 | |
|                 inherit (ssh)
 | |
|                   host
 | |
|                   ;
 | |
|                 machine = import ./nixos.nix {
 | |
|                   inherit sources system;
 | |
|                   configuration = tf-host.config.nixos-configuration;
 | |
|                 };
 | |
|                 name = "fediversity-template";
 | |
| 
 | |
|                 # worse for cross-compilation, better for pre-/post-processing, needs manual `imageSize`, random failures: https://github.com/nix-community/disko/issues/550#issuecomment-2503736973
 | |
|                 raw = "${machine.config.system.build.diskoImages}/main.raw";
 | |
| 
 | |
|                 environment = {
 | |
|                   inherit
 | |
|                     host
 | |
|                     ;
 | |
|                   node_name = node-name;
 | |
|                   image_datastore_id = imageDatastoreId;
 | |
|                 };
 | |
|               in
 | |
|               lib.trace (lib.strings.toJSON environment) pkgs.writers.writeBashBin "deploy-tf-proxmox-template.sh"
 | |
|                 (withPackages [
 | |
|                   pkgs.qemu
 | |
|                 ])
 | |
|                 ''
 | |
|                   set -e
 | |
| 
 | |
|                   # nixos-generate gives the burden of building revisions, while systemd-repart handles partitioning ~~at the burden of version revisions~~
 | |
|                   # .qcow2 is around half the size of .raw, on top of supporting backups - be it apparently at the cost of performance
 | |
|                   qemu-img convert -f raw -O qcow2 -C "${raw}" /tmp/${name}.qcow2
 | |
| 
 | |
|                   ls -l ${raw} >&2
 | |
|                   ls -l /tmp/${name}.qcow2 >&2
 | |
|                   checksum="$(sha256sum /tmp/${name}.qcow2 | cut -d " " -f1)"
 | |
| 
 | |
|                   env \
 | |
|                   TF_VAR_image=/tmp/${name}.qcow2 \
 | |
|                   TF_VAR_checksum="$checksum" \
 | |
|                   ${lib.getExe (tfApply {
 | |
|                     inherit httpBackend environment;
 | |
|                     directory = "tf-proxmox-template";
 | |
|                   })}
 | |
|                 '';
 | |
|           };
 | |
|         };
 | |
|       });
 | |
|     };
 | |
|     tf-proxmox-vm = mkOption {
 | |
|       description = ''
 | |
|         A Terraform deployment to provision and update a virtual machine on ProxmoX VE.
 | |
|         Proxmox credentials should be set using [environment variables]
 | |
|         (https://registry.terraform.io/providers/bpg/proxmox/latest/docs#environment-variables-summary)
 | |
|         with roles `PVEVMAdmin PVEDatastoreAdmin PVESDNUser`.
 | |
|       '';
 | |
|       type = submodule (tf-host: {
 | |
|         options = {
 | |
|           system = mkOption {
 | |
|             description = "The architecture of the system to deploy to.";
 | |
|             type = types.str;
 | |
|           };
 | |
|           inherit httpBackend nixos-configuration;
 | |
|           ssh = host-ssh;
 | |
|           caller = mkOption {
 | |
|             description = "The calling module to obtain the NixOS configuration from.";
 | |
|             type = types.str;
 | |
|           };
 | |
|           args = mkOption {
 | |
|             description = "The arguments with which to call the module to obtain the NixOS configuration.";
 | |
|             type = types.attrs;
 | |
|           };
 | |
|           deployment-name = mkOption {
 | |
|             description = "The name of the deployment for which to obtain the NixOS configuration.";
 | |
|             type = types.str;
 | |
|           };
 | |
|           root-path = mkOption {
 | |
|             description = "The path to the root of the repository.";
 | |
|             type = types.path;
 | |
|           };
 | |
|           node-name = mkOption {
 | |
|             description = "the name of the ProxmoX node to use.";
 | |
|             type = types.str;
 | |
|           };
 | |
|           bridge = mkOption {
 | |
|             description = "The name of the network bridge (defaults to vmbr0).";
 | |
|             type = types.str;
 | |
|             default = "vmbr0";
 | |
|           };
 | |
|           vlanId = mkOption {
 | |
|             description = "The VLAN identifier.";
 | |
|             type = types.int;
 | |
|             default = 0;
 | |
|           };
 | |
|           imageDatastoreId = mkOption {
 | |
|             description = "ID of the datastore of the image.";
 | |
|             type = types.str;
 | |
|             default = "local";
 | |
|           };
 | |
|           templateId = mkOption {
 | |
|             description = "ID of the template file from which to clone the VM.";
 | |
|             type = types.nullOr types.str;
 | |
|             example = "local:import/template.qcow2";
 | |
|           };
 | |
|           vmDatastoreId = mkOption {
 | |
|             description = "ID of the datastore of the VM.";
 | |
|             type = types.str;
 | |
|             default = "local";
 | |
|           };
 | |
|           cdDatastoreId = mkOption {
 | |
|             description = "ID of the datastore of the virtual CD-rom drive to use for cloud-init.";
 | |
|             type = types.str;
 | |
|             default = "local";
 | |
|           };
 | |
|           ipv4Gateway = mkOption {
 | |
|             description = "Gateway for IPv4.";
 | |
|             type = types.str;
 | |
|             default = "";
 | |
|           };
 | |
|           ipv4Address = mkOption {
 | |
|             description = "IPv4 address.";
 | |
|             type = types.str;
 | |
|             default = "";
 | |
|           };
 | |
|           ipv6Gateway = mkOption {
 | |
|             description = "Gateway for IPv6.";
 | |
|             type = types.str;
 | |
|             default = "";
 | |
|           };
 | |
|           ipv6Address = mkOption {
 | |
|             description = "IPv6 address.";
 | |
|             type = types.str;
 | |
|             default = "";
 | |
|           };
 | |
|           run = mkOption {
 | |
|             type = types.package;
 | |
|             # error: The option `tf-deployment.tf-host.run' is read-only, but it's set multiple times.
 | |
|             # readOnly = true;
 | |
|             default =
 | |
|               let
 | |
|                 inherit (tf-host.config)
 | |
|                   system
 | |
|                   ssh
 | |
|                   caller
 | |
|                   args
 | |
|                   deployment-name
 | |
|                   httpBackend
 | |
|                   root-path
 | |
|                   node-name
 | |
|                   bridge
 | |
|                   vlanId
 | |
|                   imageDatastoreId
 | |
|                   templateId
 | |
|                   vmDatastoreId
 | |
|                   cdDatastoreId
 | |
|                   ipv4Gateway
 | |
|                   ipv4Address
 | |
|                   ipv6Gateway
 | |
|                   ipv6Address
 | |
|                   ;
 | |
|                 inherit (ssh)
 | |
|                   host
 | |
|                   username
 | |
|                   key-file
 | |
|                   sshOpts
 | |
|                   ;
 | |
|                 deployment-type = "tf-proxmox-vm";
 | |
|                 nixos_conf = writeConfig {
 | |
|                   inherit
 | |
|                     system
 | |
|                     caller
 | |
|                     args
 | |
|                     deployment-name
 | |
|                     root-path
 | |
|                     deployment-type
 | |
|                     ;
 | |
|                 };
 | |
|                 environment = {
 | |
|                   key_file = key-file;
 | |
|                   ssh_opts = sshOpts;
 | |
|                   inherit
 | |
|                     host
 | |
|                     nixos_conf
 | |
|                     bridge
 | |
|                     ;
 | |
|                   node_name = node-name;
 | |
|                   ssh_user = username;
 | |
|                   vlan_id = vlanId;
 | |
|                   image_datastore_id = imageDatastoreId;
 | |
|                   template_id = templateId;
 | |
|                   vm_datastore_id = vmDatastoreId;
 | |
|                   cd_datastore_id = cdDatastoreId;
 | |
|                   ipv4_gateway = ipv4Gateway;
 | |
|                   ipv4_address = ipv4Address;
 | |
|                   ipv6_gateway = ipv6Gateway;
 | |
|                   ipv6_address = ipv6Address;
 | |
|                 };
 | |
|               in
 | |
|               lib.trace (lib.strings.toJSON environment) (tfApply {
 | |
|                 inherit httpBackend environment;
 | |
|                 directory = "tf-proxmox-vm";
 | |
|                 dependentDirs = [ "tf-single-host" ];
 | |
|               });
 | |
|           };
 | |
|         };
 | |
|       });
 | |
|     };
 | |
|     tf-netbox-store-ips = mkOption {
 | |
|       description = "Store a range of IPs in a Netbox instance.";
 | |
|       type = submodule (tf-netbox-store-ips: {
 | |
|         options = {
 | |
|           inherit httpBackend;
 | |
|           startAddress = mkOption {
 | |
|             description = "Start of the IP range.";
 | |
|             type = types.str;
 | |
|             example = "10.0.0.1/24";
 | |
|           };
 | |
|           endAddress = mkOption {
 | |
|             description = "End of the IP range.";
 | |
|             type = types.str;
 | |
|             example = "10.0.0.50/24";
 | |
|           };
 | |
|           run = mkOption {
 | |
|             type = types.package;
 | |
|             default =
 | |
|               let
 | |
|                 inherit (tf-netbox-store-ips.config)
 | |
|                   httpBackend
 | |
|                   startAddress
 | |
|                   endAddress
 | |
|                   ;
 | |
|               in
 | |
|               tfApply {
 | |
|                 inherit httpBackend;
 | |
|                 directory = "tf-netbox-store-ips";
 | |
|                 environment = {
 | |
|                   start_address = startAddress;
 | |
|                   end_address = endAddress;
 | |
|                 };
 | |
|               };
 | |
|           };
 | |
|         };
 | |
|       });
 | |
|     };
 | |
|     tf-netbox-get-ip = mkOption {
 | |
|       description = "Get an available IP from a Netbox instance.";
 | |
|       type = submodule (tf-netbox-get-ip: {
 | |
|         options = {
 | |
|           inherit httpBackend;
 | |
|           run = mkOption {
 | |
|             type = types.package;
 | |
|             default =
 | |
|               let
 | |
|                 inherit (tf-netbox-get-ip.config)
 | |
|                   httpBackend
 | |
|                   ;
 | |
|               in
 | |
|               tfApply {
 | |
|                 inherit httpBackend;
 | |
|                 directory = "tf-netbox-get-ip";
 | |
|                 environment = {
 | |
|                 };
 | |
|               };
 | |
|           };
 | |
|         };
 | |
|       });
 | |
|     };
 | |
|   };
 | |
| in
 | |
| {
 | |
|   options = {
 | |
|     resources = mkOption {
 | |
|       description = "Collection of deployment resources that can be required by applications and policed by hosting providers";
 | |
|       type = attrsOf (
 | |
|         submodule (
 | |
|           { ... }:
 | |
|           {
 | |
|             _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"; } ]; };
 | |
|               };
 | |
|               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;
 | |
|                       };
 | |
|                       # TODO(@fricklerhandwerk): we may want to make the function type explicit here: `application-resources -> resource-type`
 | |
|                       # and then also rename this to be consistent with the application's resource mapping
 | |
|                       options.apply = mkOption {
 | |
|                         description = "Apply the policy to a request";
 | |
|                         type = functionTo policy.config.resource-type;
 | |
|                       };
 | |
|                     })
 | |
|                   ];
 | |
|                 };
 | |
|               };
 | |
|             };
 | |
|           }
 | |
|         )
 | |
|       );
 | |
|     };
 | |
|     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;
 | |
|               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 = functionType;
 | |
|               readOnly = true;
 | |
|               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
 | |
|                 )
 | |
|               );
 | |
|             };
 | |
|             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;
 | |
|               readOnly = true;
 | |
|               default = {
 | |
|                 input-type = submodule {
 | |
|                   options = {
 | |
|                     deployment-name = mkOption {
 | |
|                       type = types.str;
 | |
|                     };
 | |
|                     required-resources = mkOption {
 | |
|                       type = attrsOf application-resources;
 | |
|                     };
 | |
|                   };
 | |
|                 };
 | |
|                 output-type = deployment-type;
 | |
|                 implementation = environment.config.implementation;
 | |
|               };
 | |
|             };
 | |
|             config-mapping = mkOption {
 | |
|               description = "Mapping from a configuration to a deployment";
 | |
|               type = functionType;
 | |
|               readOnly = true;
 | |
|               default = {
 | |
|                 input-type = submodule {
 | |
|                   options = {
 | |
|                     deployment-name = mkOption {
 | |
|                       type = types.str;
 | |
|                     };
 | |
|                     configuration = mkOption {
 | |
|                       type = config.configuration;
 | |
|                     };
 | |
|                   };
 | |
|                 };
 | |
|                 output-type = deployment-type;
 | |
|                 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;
 | |
|               readOnly = true;
 | |
|               default = environment.config.config-mapping.apply;
 | |
|             };
 | |
|           };
 | |
|         })
 | |
|       );
 | |
|     };
 | |
|     configuration = mkOption {
 | |
|       description = "Configuration type declaring options to be set by operators";
 | |
|       type = optionType;
 | |
|       readOnly = true;
 | |
|       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;
 | |
|         };
 | |
|       };
 | |
|     };
 | |
|   };
 | |
| }
 |