From 7fa3062cfb2caaf41d49ee8fb7061a3321686afa Mon Sep 17 00:00:00 2001 From: Ryan Lahfa Date: Fri, 24 May 2024 20:55:28 +0200 Subject: [PATCH 1/2] feat: disable key management modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Let the user opt-in… ! Signed-off-by: Ryan Lahfa --- src/nix/hive/eval.nix | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/nix/hive/eval.nix b/src/nix/hive/eval.nix index 787bf69..c6ec083 100644 --- a/src/nix/hive/eval.nix +++ b/src/nix/hive/eval.nix @@ -146,8 +146,6 @@ let modules = [ nixpkgsModule colmenaModules.assertionModule - colmenaModules.keyChownModule - colmenaModules.keyServiceModule colmenaOptions.deploymentOptions hive.defaults ] ++ configs; From 92c5f5c33f245c5c093164a2d7d1c90a0ed6b980 Mon Sep 17 00:00:00 2001 From: Ryan Lahfa Date: Fri, 24 May 2024 20:55:22 +0200 Subject: [PATCH 2/2] feat: generic registry and custom evaluation This PR bring custom evaluation, but does not offer yet custom activation. Therefore, you can evaluate your systems and refer to each of them, but you cannot ask Colmena to build them for you. Signed-off-by: Ryan Lahfa --- src/error.rs | 6 +++++ src/nix/hive/eval.nix | 45 ++++++++++++++++++++++++++------- src/nix/hive/mod.rs | 37 ++++++++++++++++++++++++++- src/nix/hive/options.nix | 54 ++++++++++++++++++++++++++++++++++++++++ src/nix/mod.rs | 15 +++++++++++ 5 files changed, 147 insertions(+), 10 deletions(-) diff --git a/src/error.rs b/src/error.rs index a9855e6..f0fbe11 100644 --- a/src/error.rs +++ b/src/error.rs @@ -64,6 +64,12 @@ pub enum ColmenaError { #[snafu(display("Don't know how to connect to the node"))] NoTargetHost, + #[snafu(display( + "Don't know how to deploy node: {} -- does your system type support deployment?", + node_name + ))] + UndeployableHost { node_name: String }, + #[snafu(display("Node name cannot be empty"))] EmptyNodeName, diff --git a/src/nix/hive/eval.nix b/src/nix/hive/eval.nix index c6ec083..26f17e5 100644 --- a/src/nix/hive/eval.nix +++ b/src/nix/hive/eval.nix @@ -38,19 +38,25 @@ let else if uncheckedHive ? network then uncheckedHive.network else {}; + uncheckedRegistries = if uncheckedHive ? registry then uncheckedHive.registry else {}; + # The final hive will always have the meta key instead of network. hive = let userMeta = (lib.modules.evalModules { modules = [ colmenaOptions.metaOptions uncheckedUserMeta ]; }).config; + registry = (lib.modules.evalModules { + modules = [ colmenaOptions.registryOptions { registry = uncheckedRegistries; } ]; + }).config.registry; + mergedHive = assert lib.assertMsg (!(uncheckedHive ? __schema)) '' You cannot pass in an already-evaluated Hive into the evaluator. Hint: Use the `colmenaHive` output instead of `colmena`. ''; - removeAttrs (defaultHive // uncheckedHive) [ "meta" "network" ]; + removeAttrs (defaultHive // uncheckedHive) [ "meta" "network" "registry" ]; meta = { meta = @@ -58,7 +64,7 @@ let then userMeta // { nixpkgs = ; } else userMeta; }; - in mergedHive // meta; + in mergedHive // meta // { inherit registry; }; configsFor = node: let nodeConfig = hive.${node}; @@ -112,14 +118,23 @@ let in mkNixpkgs "meta.nixpkgs" nixpkgsConf; lib = nixpkgs.lib; - reservedNames = [ "defaults" "network" "meta" ]; + reservedNames = [ "defaults" "network" "meta" "registry" ]; - evalNode = name: configs: let + evalNode = name: configs: + # Some help on error messages. + assert (lib.assertMsg (lib.hasAttrByPath [ "deployment" "systemType" ] hive.${name}) + "${name} does not have a deployment system type!"); + assert (lib.assertMsg (builtins.typeOf hive.registry == "set")) + "The hive's registry is not a set, but of type '${builtins.typeOf hive.registry}'"; + assert (lib.assertMsg (lib.hasAttr hive.${name}.deployment.systemType hive.registry) + "${builtins.toJSON (hive.${name}.deployment.systemType)} does not exist in the registry of systems!"); + let + # We cannot use `configs` because we need to access to the raw configuration fragment. + inherit (hive.registry.${hive.${name}.deployment.systemType}) evalConfig; npkgs = if hasAttr name hive.meta.nodeNixpkgs then mkNixpkgs "meta.nodeNixpkgs.${name}" hive.meta.nodeNixpkgs.${name} else nixpkgs; - evalConfig = import (npkgs.path + "/nixos/lib/eval-config.nix"); # Here we need to merge the configurations in meta.nixpkgs # and in machine config. @@ -139,15 +154,19 @@ let in lib.optional (!hasTypedConfig && length remainingKeys != 0) "The following Nixpkgs configuration keys set in meta.nixpkgs will be ignored: ${toString remainingKeys}"; - }; + } // lib.optionalAttrs (builtins.hasAttr "localSystem" npkgs || builtins.hasAttr "crossSystem" npkgs) { + nixpkgs.localSystem = lib.mkBefore npkgs.localSystem; + nixpkgs.crossSystem = lib.mkBefore npkgs.crossSystem; + }; in evalConfig { - inherit (npkgs) system; + # This doesn't exist for `evalModules` the generic way. + # inherit (npkgs) system; modules = [ nixpkgsModule colmenaModules.assertionModule colmenaOptions.deploymentOptions - hive.defaults + (hive.registry.${hive.${name}.deployment.systemType}.defaults or hive.defaults) ] ++ configs; specialArgs = { inherit name; @@ -177,6 +196,10 @@ let "allowApplyAll" ]; + serializableSystemTypeConfigKeys = [ + "supportsDeployment" + ]; + in rec { # Exported attributes __schema = "v0.20241006"; @@ -188,5 +211,9 @@ in rec { evalSelected = names: lib.filterAttrs (name: _: elem name names) toplevel; evalSelectedDrvPaths = names: lib.mapAttrs (_: v: v.drvPath) (evalSelected names); metaConfig = lib.filterAttrs (n: v: elem n metaConfigKeys) hive.meta; - introspect = f: f { inherit lib; pkgs = nixpkgs; nodes = uncheckedNodes; }; + # We cannot perform a `metaConfigKeys`-style simple check here + # because registry is arbitrarily deep and may evaluate nixpkgs indirectly. + registryConfig = lib.mapAttrs (systemTypeName: systemType: + lib.filterAttrs (n: v: elem n serializableSystemTypeConfigKeys) systemType) hive.registry; + introspect = f: f { inherit lib; pkgs = nixpkgs; inherit nodes; }; } diff --git a/src/nix/hive/mod.rs b/src/nix/hive/mod.rs index 2faa88e..7e35a03 100644 --- a/src/nix/hive/mod.rs +++ b/src/nix/hive/mod.rs @@ -16,7 +16,7 @@ use validator::Validate; use super::deployment::TargetNode; use super::{ Flake, MetaConfig, NixExpression, NixFlags, NodeConfig, NodeFilter, NodeName, - ProfileDerivation, SerializedNixExpression, StorePath, + ProfileDerivation, RegistryConfig, SerializedNixExpression, StorePath, }; use crate::error::{ColmenaError, ColmenaResult}; use crate::job::JobHandle; @@ -125,6 +125,8 @@ pub struct Hive { nix_options: HashMap, meta_config: OnceCell, + + registry_config: OnceCell, } struct NixInstantiate<'hive> { @@ -179,6 +181,7 @@ impl Hive { impure: false, nix_options: HashMap::new(), meta_config: OnceCell::new(), + registry_config: OnceCell::new(), }) } @@ -205,6 +208,17 @@ impl Hive { self.evaluation_method = method; } + pub async fn get_registry_config(&self) -> ColmenaResult<&RegistryConfig> { + self.registry_config + .get_or_try_init(|| async { + self.nix_instantiate("hive.registryConfig") + .eval() + .capture_json() + .await + }) + .await + } + pub fn set_show_trace(&mut self, value: bool) { self.show_trace = value; } @@ -248,6 +262,9 @@ impl Hive { ) -> ColmenaResult> { let mut node_configs = None; + log::info!("Enumerating systems..."); + let registry = self.get_registry_config().await?; + log::info!("Enumerating nodes..."); let all_nodes = self.node_names().await?; @@ -281,6 +298,24 @@ impl Hive { self.deployment_info_selected(&selected_nodes).await? }; + for node_config in &node_configs { + if let Some(system_type) = node_config.1.system_type.as_ref() { + let Some(system_config) = registry.systems.get(system_type) else { + // TODO: convert me to proper error? + log::warn!("'{:?}' is not a known system type in the registry, double check your expressions!", system_type); + return Err(ColmenaError::Unknown { + message: "unknown system type".to_string(), + }); + }; + + if !system_config.supports_deployment { + return Err(ColmenaError::UndeployableHost { + node_name: node_config.0.to_string(), + }); + } + } + } + let mut targets = HashMap::new(); let mut n_ssh = 0; for node in selected_nodes.into_iter() { diff --git a/src/nix/hive/options.nix b/src/nix/hive/options.nix index 3f56642..04108ff 100644 --- a/src/nix/hive/options.nix +++ b/src/nix/hive/options.nix @@ -96,6 +96,14 @@ with builtins; rec { in { options = { deployment = { + systemType = lib.mkOption { + description = mdDoc '' + System type used for this node, e.g. NixOS. + ''; + default = "nixos"; + # TODO: enum among all registered systems? + type = types.str; + }; targetHost = lib.mkOption { description = '' The target SSH node for deployment. @@ -216,6 +224,52 @@ with builtins; rec { }; }; }; + # Options for a registered system type + systemTypeOptions = { name, lib, ... }: let + inherit (lib) types; + mdDoc = lib.mdDoc or lib.id; + in + { + options = { + evalConfig = lib.mkOption { + description = mdDoc '' + Evaluation function which share the same interface as `nixos/lib/eval-config.nix` + which can be tailored to your own usecases or to target another type of system, + e.g. nix-darwin. + ''; + type = types.functionTo types.unspecified; + }; + supportsDeployment = lib.mkOption { + description = mdDoc '' + Whether this system type supports deployment or not. + + If it supports deployment, it needs to have appropriate activation code, + refer to how to write custom activators. + ''; + default = name == "nixos"; + defaultText = "If a NixOS system, then true, otherwise false by default"; + }; + defaults = lib.mkOption { + description = mdDoc '' + Default configuration for that system type. + ''; + type = types.functionTo types.unspecified; + default = _: {}; + }; + }; + }; + registryOptions = { lib, ... }: let + inherit (lib) types; + mdDoc = lib.mdDoc or lib.id; + in + { + options.registry = lib.mkOption { + description = mdDoc '' + A registry of all system types. + ''; + type = types.attrsOf (types.submodule systemTypeOptions); + }; + }; # Hive-wide options metaOptions = { lib, ... }: let inherit (lib) types; diff --git a/src/nix/mod.rs b/src/nix/mod.rs index 15be22c..4823f74 100644 --- a/src/nix/mod.rs +++ b/src/nix/mod.rs @@ -55,6 +55,9 @@ pub struct NodeName(#[serde(deserialize_with = "NodeName::deserialize")] String) #[derive(Debug, Clone, Validate, Deserialize)] pub struct NodeConfig { + #[serde(rename = "systemType")] + system_type: Option, + #[serde(rename = "targetHost")] target_host: Option, @@ -94,6 +97,18 @@ pub struct MetaConfig { pub machines_file: Option, } +#[derive(Debug, Clone, Validate, Deserialize)] +pub struct SystemTypeConfig { + #[serde(rename = "supportsDeployment")] + pub supports_deployment: bool, +} + +#[derive(Debug, Clone, Validate, Deserialize)] +pub struct RegistryConfig { + #[serde(flatten)] + pub systems: HashMap, +} + /// Nix CLI flags. #[derive(Debug, Clone, Default)] pub struct NixFlags {