forked from DGNum/colmena
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 <ryan@dgnum.eu>
This commit is contained in:
parent
7fa3062cfb
commit
92c5f5c33f
5 changed files with 147 additions and 10 deletions
|
@ -64,6 +64,12 @@ pub enum ColmenaError {
|
||||||
#[snafu(display("Don't know how to connect to the node"))]
|
#[snafu(display("Don't know how to connect to the node"))]
|
||||||
NoTargetHost,
|
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"))]
|
#[snafu(display("Node name cannot be empty"))]
|
||||||
EmptyNodeName,
|
EmptyNodeName,
|
||||||
|
|
||||||
|
|
|
@ -38,19 +38,25 @@ let
|
||||||
else if uncheckedHive ? network then uncheckedHive.network
|
else if uncheckedHive ? network then uncheckedHive.network
|
||||||
else {};
|
else {};
|
||||||
|
|
||||||
|
uncheckedRegistries = if uncheckedHive ? registry then uncheckedHive.registry else {};
|
||||||
|
|
||||||
# The final hive will always have the meta key instead of network.
|
# The final hive will always have the meta key instead of network.
|
||||||
hive = let
|
hive = let
|
||||||
userMeta = (lib.modules.evalModules {
|
userMeta = (lib.modules.evalModules {
|
||||||
modules = [ colmenaOptions.metaOptions uncheckedUserMeta ];
|
modules = [ colmenaOptions.metaOptions uncheckedUserMeta ];
|
||||||
}).config;
|
}).config;
|
||||||
|
|
||||||
|
registry = (lib.modules.evalModules {
|
||||||
|
modules = [ colmenaOptions.registryOptions { registry = uncheckedRegistries; } ];
|
||||||
|
}).config.registry;
|
||||||
|
|
||||||
mergedHive =
|
mergedHive =
|
||||||
assert lib.assertMsg (!(uncheckedHive ? __schema)) ''
|
assert lib.assertMsg (!(uncheckedHive ? __schema)) ''
|
||||||
You cannot pass in an already-evaluated Hive into the evaluator.
|
You cannot pass in an already-evaluated Hive into the evaluator.
|
||||||
|
|
||||||
Hint: Use the `colmenaHive` output instead of `colmena`.
|
Hint: Use the `colmenaHive` output instead of `colmena`.
|
||||||
'';
|
'';
|
||||||
removeAttrs (defaultHive // uncheckedHive) [ "meta" "network" ];
|
removeAttrs (defaultHive // uncheckedHive) [ "meta" "network" "registry" ];
|
||||||
|
|
||||||
meta = {
|
meta = {
|
||||||
meta =
|
meta =
|
||||||
|
@ -58,7 +64,7 @@ let
|
||||||
then userMeta // { nixpkgs = <nixpkgs>; }
|
then userMeta // { nixpkgs = <nixpkgs>; }
|
||||||
else userMeta;
|
else userMeta;
|
||||||
};
|
};
|
||||||
in mergedHive // meta;
|
in mergedHive // meta // { inherit registry; };
|
||||||
|
|
||||||
configsFor = node: let
|
configsFor = node: let
|
||||||
nodeConfig = hive.${node};
|
nodeConfig = hive.${node};
|
||||||
|
@ -112,14 +118,23 @@ let
|
||||||
in mkNixpkgs "meta.nixpkgs" nixpkgsConf;
|
in mkNixpkgs "meta.nixpkgs" nixpkgsConf;
|
||||||
|
|
||||||
lib = nixpkgs.lib;
|
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 =
|
npkgs =
|
||||||
if hasAttr name hive.meta.nodeNixpkgs
|
if hasAttr name hive.meta.nodeNixpkgs
|
||||||
then mkNixpkgs "meta.nodeNixpkgs.${name}" hive.meta.nodeNixpkgs.${name}
|
then mkNixpkgs "meta.nodeNixpkgs.${name}" hive.meta.nodeNixpkgs.${name}
|
||||||
else nixpkgs;
|
else nixpkgs;
|
||||||
evalConfig = import (npkgs.path + "/nixos/lib/eval-config.nix");
|
|
||||||
|
|
||||||
# Here we need to merge the configurations in meta.nixpkgs
|
# Here we need to merge the configurations in meta.nixpkgs
|
||||||
# and in machine config.
|
# and in machine config.
|
||||||
|
@ -139,15 +154,19 @@ let
|
||||||
in
|
in
|
||||||
lib.optional (!hasTypedConfig && length remainingKeys != 0)
|
lib.optional (!hasTypedConfig && length remainingKeys != 0)
|
||||||
"The following Nixpkgs configuration keys set in meta.nixpkgs will be ignored: ${toString remainingKeys}";
|
"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 {
|
in evalConfig {
|
||||||
inherit (npkgs) system;
|
# This doesn't exist for `evalModules` the generic way.
|
||||||
|
# inherit (npkgs) system;
|
||||||
|
|
||||||
modules = [
|
modules = [
|
||||||
nixpkgsModule
|
nixpkgsModule
|
||||||
colmenaModules.assertionModule
|
colmenaModules.assertionModule
|
||||||
colmenaOptions.deploymentOptions
|
colmenaOptions.deploymentOptions
|
||||||
hive.defaults
|
(hive.registry.${hive.${name}.deployment.systemType}.defaults or hive.defaults)
|
||||||
] ++ configs;
|
] ++ configs;
|
||||||
specialArgs = {
|
specialArgs = {
|
||||||
inherit name;
|
inherit name;
|
||||||
|
@ -177,6 +196,10 @@ let
|
||||||
"allowApplyAll"
|
"allowApplyAll"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
serializableSystemTypeConfigKeys = [
|
||||||
|
"supportsDeployment"
|
||||||
|
];
|
||||||
|
|
||||||
in rec {
|
in rec {
|
||||||
# Exported attributes
|
# Exported attributes
|
||||||
__schema = "v0.20241006";
|
__schema = "v0.20241006";
|
||||||
|
@ -188,5 +211,9 @@ in rec {
|
||||||
evalSelected = names: lib.filterAttrs (name: _: elem name names) toplevel;
|
evalSelected = names: lib.filterAttrs (name: _: elem name names) toplevel;
|
||||||
evalSelectedDrvPaths = names: lib.mapAttrs (_: v: v.drvPath) (evalSelected names);
|
evalSelectedDrvPaths = names: lib.mapAttrs (_: v: v.drvPath) (evalSelected names);
|
||||||
metaConfig = lib.filterAttrs (n: v: elem n metaConfigKeys) hive.meta;
|
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; };
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ use validator::Validate;
|
||||||
use super::deployment::TargetNode;
|
use super::deployment::TargetNode;
|
||||||
use super::{
|
use super::{
|
||||||
Flake, MetaConfig, NixExpression, NixFlags, NodeConfig, NodeFilter, NodeName,
|
Flake, MetaConfig, NixExpression, NixFlags, NodeConfig, NodeFilter, NodeName,
|
||||||
ProfileDerivation, SerializedNixExpression, StorePath,
|
ProfileDerivation, RegistryConfig, SerializedNixExpression, StorePath,
|
||||||
};
|
};
|
||||||
use crate::error::{ColmenaError, ColmenaResult};
|
use crate::error::{ColmenaError, ColmenaResult};
|
||||||
use crate::job::JobHandle;
|
use crate::job::JobHandle;
|
||||||
|
@ -125,6 +125,8 @@ pub struct Hive {
|
||||||
nix_options: HashMap<String, String>,
|
nix_options: HashMap<String, String>,
|
||||||
|
|
||||||
meta_config: OnceCell<MetaConfig>,
|
meta_config: OnceCell<MetaConfig>,
|
||||||
|
|
||||||
|
registry_config: OnceCell<RegistryConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct NixInstantiate<'hive> {
|
struct NixInstantiate<'hive> {
|
||||||
|
@ -179,6 +181,7 @@ impl Hive {
|
||||||
impure: false,
|
impure: false,
|
||||||
nix_options: HashMap::new(),
|
nix_options: HashMap::new(),
|
||||||
meta_config: OnceCell::new(),
|
meta_config: OnceCell::new(),
|
||||||
|
registry_config: OnceCell::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,6 +208,17 @@ impl Hive {
|
||||||
self.evaluation_method = method;
|
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) {
|
pub fn set_show_trace(&mut self, value: bool) {
|
||||||
self.show_trace = value;
|
self.show_trace = value;
|
||||||
}
|
}
|
||||||
|
@ -248,6 +262,9 @@ impl Hive {
|
||||||
) -> ColmenaResult<HashMap<NodeName, TargetNode>> {
|
) -> ColmenaResult<HashMap<NodeName, TargetNode>> {
|
||||||
let mut node_configs = None;
|
let mut node_configs = None;
|
||||||
|
|
||||||
|
log::info!("Enumerating systems...");
|
||||||
|
let registry = self.get_registry_config().await?;
|
||||||
|
|
||||||
log::info!("Enumerating nodes...");
|
log::info!("Enumerating nodes...");
|
||||||
|
|
||||||
let all_nodes = self.node_names().await?;
|
let all_nodes = self.node_names().await?;
|
||||||
|
@ -281,6 +298,24 @@ impl Hive {
|
||||||
self.deployment_info_selected(&selected_nodes).await?
|
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 targets = HashMap::new();
|
||||||
let mut n_ssh = 0;
|
let mut n_ssh = 0;
|
||||||
for node in selected_nodes.into_iter() {
|
for node in selected_nodes.into_iter() {
|
||||||
|
|
|
@ -96,6 +96,14 @@ with builtins; rec {
|
||||||
in {
|
in {
|
||||||
options = {
|
options = {
|
||||||
deployment = {
|
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 {
|
targetHost = lib.mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
The target SSH node for deployment.
|
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
|
# Hive-wide options
|
||||||
metaOptions = { lib, ... }: let
|
metaOptions = { lib, ... }: let
|
||||||
inherit (lib) types;
|
inherit (lib) types;
|
||||||
|
|
|
@ -55,6 +55,9 @@ pub struct NodeName(#[serde(deserialize_with = "NodeName::deserialize")] String)
|
||||||
|
|
||||||
#[derive(Debug, Clone, Validate, Deserialize)]
|
#[derive(Debug, Clone, Validate, Deserialize)]
|
||||||
pub struct NodeConfig {
|
pub struct NodeConfig {
|
||||||
|
#[serde(rename = "systemType")]
|
||||||
|
system_type: Option<String>,
|
||||||
|
|
||||||
#[serde(rename = "targetHost")]
|
#[serde(rename = "targetHost")]
|
||||||
target_host: Option<String>,
|
target_host: Option<String>,
|
||||||
|
|
||||||
|
@ -94,6 +97,18 @@ pub struct MetaConfig {
|
||||||
pub machines_file: Option<String>,
|
pub machines_file: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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<String, SystemTypeConfig>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Nix CLI flags.
|
/// Nix CLI flags.
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct NixFlags {
|
pub struct NixFlags {
|
||||||
|
|
Loading…
Reference in a new issue