From f38c7e8a75c769f5b1040b03035aa6211fecaa95 Mon Sep 17 00:00:00 2001 From: marshmallow Date: Sun, 22 Jun 2025 17:14:37 +1000 Subject: [PATCH] switch to a makeHive model (#187) --- CHANGELOG.md | 6 ++ doc/guide/getting-started.md | 22 ++++-- doc/guide/hive-default.md | 7 +- doc/guide/keys.md | 42 +++++++---- doc/guide/targeting.md | 7 +- doc/reference/meta.md | 10 +-- flake.nix | 5 ++ justfile | 2 +- nix/shells.nix | 1 - nix/utils.nix | 1 - runtime/evaluate.nix | 10 +-- runtime/makeHive.nix | 11 +++ tests/nix/default.nix | 14 ++-- tests/nix/suite/test_keys/hive.nix | 4 +- tests/nix/suite/test_local_deploy/hive.nix | 4 +- tests/nix/suite/test_remote_deploy/hive.nix | 4 +- tests/nix/suite/utils.nix | 2 +- tests/rust/_keys_should_fail/hive.nix | 5 +- tests/rust/default_values_match/hive.nix | 5 +- tests/rust/flake_hive/flake.lock | 1 - tests/rust/flake_hive/flake.nix | 10 ++- tests/rust/non_trivial_hive/hive.nix | 5 +- .../rust/test_hive_dot_nix_priority/hive.nix | 6 +- tests/rust/test_hive_file/hive.nix | 5 +- wire/cli/default.nix | 8 +-- wire/cli/src/apply.rs | 32 ++++----- wire/cli/src/cli.rs | 41 ++++++----- wire/cli/src/main.rs | 19 +---- wire/lib/src/hive/mod.rs | 51 ++++++++++--- wire/lib/src/hive/node.rs | 8 ++- wire/lib/src/hive/steps/evaluate.rs | 4 +- wire/lib/src/lib.rs | 2 +- wire/lib/src/nix.rs | 72 +++++++------------ wire/lib/src/test_support.rs | 19 +++++ 34 files changed, 271 insertions(+), 174 deletions(-) create mode 100644 runtime/makeHive.nix delete mode 120000 tests/rust/flake_hive/flake.lock diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c50a73..f69793b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Wire will now properly respect `deployment.target.hosts`. - Wire will now attempt each target host in order until a valid one is found. +### Changed + +- Wire now directly evaluates your hive instead of shipping extra nix code along with its binary. + You must now use `outputs.makeHive { ... }` instead of a raw attribute. + This can be obtained with npins or a flake input. + ## [0.3.0] - 2025-06-20 ### Added diff --git a/doc/guide/getting-started.md b/doc/guide/getting-started.md index 916af75..62cfc2b 100644 --- a/doc/guide/getting-started.md +++ b/doc/guide/getting-started.md @@ -43,12 +43,23 @@ Wire groups your machines into _nodes_, which are NixOS configurations with additional information for deployment. Start by creating a `hive.nix` in the same directory as your `configuration.nix`. +::: info + +To include wire in these examples, we are using +[npins](https://github.com/andir/npins). To create this setup you +would run `npins add github wires-org wire`. + +::: + A `hive.nix` is an attribute set with NixOS configurations, each with a unique name. Add a node for your local machine: ```nix:line-numbers [hive.nix] -{ - meta.nixpkgs = import {}; +let + sources = import ./npins; + wire = import sources.wire; +in wire.makeHive { + meta.nixpkgs = import sources.nixpkgs { }; my-local-machine = { imports = [./configuration.nix]; @@ -73,8 +84,11 @@ wire apply switch -v Lets add another node to your hive! This one is an example of a remote machine. ```nix:line-numbers [hive.nix] -{ - meta.nixpkgs = import {}; +let + sources = import ./npins; + wire = import sources.wire; +in wire.makeHive { + meta.nixpkgs = import sources.nixpkgs { }; my-local-machine = { imports = [./local-machine/configuration.nix]; diff --git a/doc/guide/hive-default.md b/doc/guide/hive-default.md index 521493a..0368fe3 100644 --- a/doc/guide/hive-default.md +++ b/doc/guide/hive-default.md @@ -21,8 +21,11 @@ node may rely on modules that default imports. ::: ```nix:line-numbers [hive.nix] -{ - meta.nixpkgs = import {}; +let + sources = import ./npins; + wire = import sources.wire; +in wire.makeHive { + meta.nixpkgs = import sources.nixpkgs { }; defaults = { # name of the node that defaults is being applied to diff --git a/doc/guide/keys.md b/doc/guide/keys.md index d297b91..306f39d 100644 --- a/doc/guide/keys.md +++ b/doc/guide/keys.md @@ -31,8 +31,11 @@ work well with wire keys include: ### A Trivial "Key" ```nix:line-numbers [hive.nix] -{ - meta.nixpkgs = import {}; +let + sources = import ./npins; + wire = import sources.wire; +in wire.makeHive { + meta.nixpkgs = import sources.nixpkgs { }; node-1 = { deployment.key."file.txt" = { @@ -52,8 +55,11 @@ Hello World! ### Encrypting with GPG ```nix:line-numbers [hive.nix] -{ - meta.nixpkgs = import {}; +let + sources = import ./npins; + wire = import sources.wire; +in wire.makeHive { + meta.nixpkgs = import sources.nixpkgs { }; node-1 = { deployment.key."file.txt" = { @@ -75,8 +81,11 @@ Hello World! ### A Plain Text File ```nix:line-numbers [hive.nix] -{ - meta.nixpkgs = import {}; +let + sources = import ./npins; + wire = import sources.wire; +in wire.makeHive { + meta.nixpkgs = import sources.nixpkgs { }; node-1 = { deployment.key."file.txt" = { @@ -108,8 +117,11 @@ Wire secrets are owned by user & group `root` (`0600`). You can change these with the `user` and `group` option. ```nix:line-numbers [hive.nix] -{ - meta.nixpkgs = import {}; +let + sources = import ./npins; + wire = import sources.wire; +in wire.makeHive { + meta.nixpkgs = import sources.nixpkgs { }; node-1 = { deployment.key."file.txt" = { @@ -135,8 +147,11 @@ You can access the full absolute path of any key with Here's an example with the Tailscale service: ```nix:line-numbers [hive.nix] -{ - meta.nixpkgs = import {}; +let + sources = import ./npins; + wire = import sources.wire; +in wire.makeHive { + meta.nixpkgs = import sources.nixpkgs { }; node-1 = {config, ...}: { services.tailscale = { @@ -159,8 +174,11 @@ further reduce duplication using the `config` argument. Here's an example of providing a certificate that is only readable by the caddy service. ```nix:line-numbers [hive.nix] -{ - meta.nixpkgs = import {}; +let + sources = import ./npins; + wire = import sources.wire; +in wire.makeHive { + meta.nixpkgs = import sources.nixpkgs { }; some-web-server = {config, ...}: { deployment.keys."some.host.pem" = { diff --git a/doc/guide/targeting.md b/doc/guide/targeting.md index c278349..ea8c459 100644 --- a/doc/guide/targeting.md +++ b/doc/guide/targeting.md @@ -14,8 +14,11 @@ Nodes can have _tags_, which allows you to easily target multiple, related nodes for deployment. ```nix:line-numbers [hive.nix] -{ - meta.nixpkgs = import {}; +let + sources = import ./npins; + wire = import sources.wire; +in wire.makeHive { + meta.nixpkgs = import sources.nixpkgs { }; node-1 = { # ... diff --git a/doc/reference/meta.md b/doc/reference/meta.md index 0b98ca2..9bebd2b 100644 --- a/doc/reference/meta.md +++ b/doc/reference/meta.md @@ -15,9 +15,7 @@ default to `inputs.nixpkgs`. _Type:_ A path or an instance of `nixpkgs`. -_Default (`hive.nix`):_ `null` - -_Default (flake attribute):_ `inputs.nixpkgs.outPath` +_Default:_ `inputs.nixpkgs.outPath` _Examples:_ @@ -27,7 +25,11 @@ _Examples:_ meta.nixpkgs = ; - meta.nixpkgs = import {}; + meta.nixpkgs = import { }; + + meta.nixpkgs = import sources.nixpkgs { }; + + meta.nixpkgs = inputs.nixpkgs.outPath; meta.nixpkgs = inputs.other-nixpkgs.outPath; } diff --git a/flake.nix b/flake.nix index 3134fbf..66f5a32 100644 --- a/flake.nix +++ b/flake.nix @@ -41,6 +41,11 @@ ]; systems = import systems; + flake = { + nixosModules.default = import ./runtime/module.nix; + makeHive = import ./runtime/makeHive.nix; + }; + perSystem = { pkgs, diff --git a/justfile b/justfile index bfd00a7..5b6f96d 100644 --- a/justfile +++ b/justfile @@ -4,4 +4,4 @@ build-dhat: cargo build --profile profiling --features dhat-heap @echo 'dhat binaries in target/profiling' @echo 'Example:' - @echo 'WIRE_RUNTIME=/nix/store/...-runtime WIRE_KEY_AGENT=/nix/store/...-key_agent-0.1.0 PROJECT/target/profiling/wire apply ...' + @echo 'WIRE_KEY_AGENT=/nix/store/...-key_agent-0.1.0 PROJECT/target/profiling/wire apply ...' diff --git a/nix/shells.nix b/nix/shells.nix index 3500704..8fbdc64 100644 --- a/nix/shells.nix +++ b/nix/shells.nix @@ -28,7 +28,6 @@ shellHook = builtins.concatStringsSep "\n" [ cfg.installationScript '' - export WIRE_RUNTIME=$(realpath ./runtime) export WIRE_TEST_DIR=$(realpath ./tests/rust) '' ]; diff --git a/nix/utils.nix b/nix/utils.nix index dd54787..6e685d2 100644 --- a/nix/utils.nix +++ b/nix/utils.nix @@ -27,7 +27,6 @@ commonArgs = { inherit src; strictDeps = true; - WIRE_RUNTIME = ../runtime; WIRE_TEST_DIR = ../tests/rust; PROTOC = lib.getExe pkgs.protobuf; }; diff --git a/runtime/evaluate.nix b/runtime/evaluate.nix index 37b2ec7..87027af 100644 --- a/runtime/evaluate.nix +++ b/runtime/evaluate.nix @@ -1,8 +1,6 @@ { hive, - path, nixosConfigurations ? { }, - nixpkgs ? null, }: let module = import ./module.nix; @@ -38,7 +36,7 @@ let else mergedHive.meta.nixpkgs else - import nixpkgs { }; + builtins.abort "makeHive called without meta.nixpkgs specified."; evaluateNode = name: @@ -67,10 +65,12 @@ let getTopLevel = node: (evaluateNode node).config.system.build.toplevel.drvPath; in rec { - inherit evaluateNode getTopLevel nodes; + inherit nodes; + topLevels = builtins.mapAttrs (name: _: getTopLevel name) nodes; inspect = { - inherit path; + _schema = 0; + nodes = builtins.mapAttrs (_: v: v.config.deployment) nodes; }; } diff --git a/runtime/makeHive.nix b/runtime/makeHive.nix new file mode 100644 index 0000000..96156a9 --- /dev/null +++ b/runtime/makeHive.nix @@ -0,0 +1,11 @@ +{ + nixosConfigurations ? { }, + ... +}@hive: +import ./evaluate.nix { + inherit + nixosConfigurations + ; + + hive = builtins.removeAttrs hive [ "nixosConfigurations" ]; +} diff --git a/tests/nix/default.nix b/tests/nix/default.nix index c944b63..99efe52 100644 --- a/tests/nix/default.nix +++ b/tests/nix/default.nix @@ -101,18 +101,13 @@ in defaults = { pkgs, - evaluateHive, ... }: let - hive = evaluateHive { - nixpkgs = pkgs.path; - path = injectedFlakeDir; - hive = builtins.scopedImport { - __nixPath = _b: null; - __findFile = path: name: if name == "nixpkgs" then pkgs.path else throw "oops!!"; - } "${injectedFlakeDir}/${path}/hive.nix"; - }; + hive = builtins.scopedImport { + __nixPath = _b: null; + __findFile = path: name: if name == "nixpkgs" then pkgs.path else throw "oops!!"; + } "${injectedFlakeDir}/${path}/hive.nix"; nodes = mapAttrsToList (_: val: val.config.system.build.toplevel.drvPath) hive.nodes; # fetch **all** dependencies of a flake # it's called fetchLayer because my naming skills are awful @@ -143,7 +138,6 @@ in ]; }; node.specialArgs = { - evaluateHive = import "${self}/runtime/evaluate.nix"; testName = name; snakeOil = import "${pkgs.path}/nixos/tests/ssh-keys.nix" pkgs; inherit (opts) testDir; diff --git a/tests/nix/suite/test_keys/hive.nix b/tests/nix/suite/test_keys/hive.nix index 5bd810d..f485387 100644 --- a/tests/nix/suite/test_keys/hive.nix +++ b/tests/nix/suite/test_keys/hive.nix @@ -1,7 +1,7 @@ let - mkHiveNode = import ../utils.nix { testName = "test_keys-@IDENT@"; }; + inherit (import ../utils.nix { testName = "test_keys-@IDENT@"; }) makeHive mkHiveNode; in -{ +makeHive { meta.nixpkgs = import { system = "x86_64-linux"; }; defaults = { deployment.keys = { diff --git a/tests/nix/suite/test_local_deploy/hive.nix b/tests/nix/suite/test_local_deploy/hive.nix index 48bed89..2b8f910 100644 --- a/tests/nix/suite/test_local_deploy/hive.nix +++ b/tests/nix/suite/test_local_deploy/hive.nix @@ -1,7 +1,7 @@ let - mkHiveNode = import ../utils.nix { testName = "test_local_deploy-@IDENT@"; }; + inherit (import ../utils.nix { testName = "test_keys-@IDENT@"; }) makeHive mkHiveNode; in -{ +makeHive { meta.nixpkgs = import { system = "x86_64-linux"; }; deployer = mkHiveNode { hostname = "deployer"; } { environment.etc."a".text = "b"; diff --git a/tests/nix/suite/test_remote_deploy/hive.nix b/tests/nix/suite/test_remote_deploy/hive.nix index 2a4f842..72fec69 100644 --- a/tests/nix/suite/test_remote_deploy/hive.nix +++ b/tests/nix/suite/test_remote_deploy/hive.nix @@ -1,7 +1,7 @@ let - mkHiveNode = import ../utils.nix { testName = "test_remote_deploy-@IDENT@"; }; + inherit (import ../utils.nix { testName = "test_keys-@IDENT@"; }) makeHive mkHiveNode; in -{ +makeHive { meta.nixpkgs = import { system = "x86_64-linux"; }; receiver = mkHiveNode { hostname = "receiver"; } { environment.etc."a".text = "b"; diff --git a/tests/nix/suite/utils.nix b/tests/nix/suite/utils.nix index 2ce0125..a7b2617 100644 --- a/tests/nix/suite/utils.nix +++ b/tests/nix/suite/utils.nix @@ -46,5 +46,5 @@ in ]; }; - __functor = self: self.mkHiveNode; + inherit (flake) makeHive; } diff --git a/tests/rust/_keys_should_fail/hive.nix b/tests/rust/_keys_should_fail/hive.nix index 67bee48..a28b3a0 100644 --- a/tests/rust/_keys_should_fail/hive.nix +++ b/tests/rust/_keys_should_fail/hive.nix @@ -1,4 +1,7 @@ -{ +let + inherit (import ../../..) makeHive; +in +makeHive { meta = { nixpkgs = ; }; diff --git a/tests/rust/default_values_match/hive.nix b/tests/rust/default_values_match/hive.nix index 20bd724..01dddd4 100644 --- a/tests/rust/default_values_match/hive.nix +++ b/tests/rust/default_values_match/hive.nix @@ -1,4 +1,7 @@ -{ +let + inherit (import ../../..) makeHive; +in +makeHive { meta = { nixpkgs = ; }; diff --git a/tests/rust/flake_hive/flake.lock b/tests/rust/flake_hive/flake.lock deleted file mode 120000 index 484b1e5..0000000 --- a/tests/rust/flake_hive/flake.lock +++ /dev/null @@ -1 +0,0 @@ -../../../flake.lock \ No newline at end of file diff --git a/tests/rust/flake_hive/flake.nix b/tests/rust/flake_hive/flake.nix index 1c0bdfc..b767cb6 100644 --- a/tests/rust/flake_hive/flake.nix +++ b/tests/rust/flake_hive/flake.nix @@ -2,9 +2,15 @@ inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; outputs = - { nixpkgs, ... }@inputs: + { nixpkgs, self, ... }@inputs: + let + makeHive = import ./makeHive.nix; + in { - colmena = { + colmena = makeHive { + inherit (self) nixosConfigurations; + meta.nixpkgs = import nixpkgs { system = "x86_64-linux"; }; + node-a = { }; node-b = { nixpkgs.hostPlatform = "x86_64-linux"; diff --git a/tests/rust/non_trivial_hive/hive.nix b/tests/rust/non_trivial_hive/hive.nix index d760621..ff6372f 100644 --- a/tests/rust/non_trivial_hive/hive.nix +++ b/tests/rust/non_trivial_hive/hive.nix @@ -1,4 +1,7 @@ -{ +let + inherit (import ../../..) makeHive; +in +makeHive { meta = { nixpkgs = ; }; diff --git a/tests/rust/test_hive_dot_nix_priority/hive.nix b/tests/rust/test_hive_dot_nix_priority/hive.nix index ffcd441..e3f08b6 100644 --- a/tests/rust/test_hive_dot_nix_priority/hive.nix +++ b/tests/rust/test_hive_dot_nix_priority/hive.nix @@ -1 +1,5 @@ -{ } +let + inherit (import ../../..) makeHive; +in +makeHive { +} diff --git a/tests/rust/test_hive_file/hive.nix b/tests/rust/test_hive_file/hive.nix index 198b942..7470770 100644 --- a/tests/rust/test_hive_file/hive.nix +++ b/tests/rust/test_hive_file/hive.nix @@ -1,4 +1,7 @@ -{ +let + inherit (import ../../..) makeHive; +in +makeHive { meta = { nixpkgs = ; }; diff --git a/wire/cli/default.nix b/wire/cli/default.nix index b39c84e..c4e59ff 100644 --- a/wire/cli/default.nix +++ b/wire/cli/default.nix @@ -10,10 +10,6 @@ ... }: let - postBuild = '' - wrapProgram $out/bin/wire \ - --set WIRE_RUNTIME ${../../runtime} \ - ''; cleanSystem = system: lib.replaceStrings [ "-" ] [ "_" ] system; agents = lib.strings.concatMapStrings ( system: "--set WIRE_KEY_AGENT_${cleanSystem system} ${(getSystem system).packages.agent} " @@ -43,7 +39,7 @@ pkgs.makeWrapper ]; postBuild = '' - ${postBuild} ${agents} + wrapProgram $out/bin/wire ${agents} ''; meta.mainProgram = "wire"; }; @@ -55,7 +51,7 @@ pkgs.makeWrapper ]; postBuild = '' - ${postBuild} --set WIRE_KEY_AGENT_${cleanSystem system} ${self'.packages.agent} + wrapProgram $out/bin/wire --set WIRE_KEY_AGENT_${cleanSystem system} ${self'.packages.agent} ''; meta.mainProgram = "wire"; }; diff --git a/wire/cli/src/apply.rs b/wire/cli/src/apply.rs index 299257b..e3cd4f4 100644 --- a/wire/cli/src/apply.rs +++ b/wire/cli/src/apply.rs @@ -4,22 +4,20 @@ use indicatif::ProgressStyle; use itertools::{Either, Itertools}; use lib::SubCommandModifiers; use lib::hive::Hive; -use lib::hive::node::{Context, Goal, GoalExecutor, StepState}; +use lib::hive::node::{Context, GoalExecutor, StepState}; use std::collections::HashSet; use std::fmt::Write; +use std::path::PathBuf; use tracing::{Span, error, info, instrument}; use tracing_indicatif::span_ext::IndicatifSpanExt; -use crate::cli::ApplyTarget; +use crate::cli::{ApplyArgs, ApplyTarget}; -#[instrument(skip_all, fields(goal = %goal, on = %on.iter().join(", ")))] +#[instrument(skip_all, fields(goal = %args.goal, on = %args.on.iter().join(", ")))] pub async fn apply( hive: &mut Hive, - goal: Goal, - on: Vec, - parallel: usize, - no_keys: bool, - always_build_local: Vec, + args: ApplyArgs, + path: PathBuf, modifiers: SubCommandModifiers, ) -> Result<(), anyhow::Error> { let header_span = Span::current(); @@ -27,11 +25,11 @@ pub async fn apply( header_span.pb_set_length(1); // Respect user's --always-build-local arg - hive.force_always_local(always_build_local)?; + hive.force_always_local(args.always_build_local)?; let header_span_enter = header_span.enter(); - let (tags, names) = on.iter().fold( + let (tags, names) = args.on.iter().fold( (HashSet::new(), HashSet::new()), |(mut tags, mut names), target| { match target { @@ -46,20 +44,22 @@ pub async fn apply( .nodes .iter_mut() .filter(|(name, node)| { - on.is_empty() || names.contains(name) || node.tags.iter().any(|tag| tags.contains(tag)) + args.on.is_empty() + || names.contains(name) + || node.tags.iter().any(|tag| tags.contains(tag)) }) .map(|node| { - let path = hive.path.clone(); + let path = path.clone(); let span = header_span.clone(); - info!("Resolved {on:?} to include {}", node.0); + info!("Resolved {:?} to include {}", args.on, node.0); let context = Context { node: node.1, name: node.0, - goal, + goal: args.goal.clone().try_into().unwrap(), state: StepState::default(), - no_keys, + no_keys: args.no_keys, hivepath: path, modifiers, }; @@ -74,7 +74,7 @@ pub async fn apply( error!("There are no nodes selected for deployment"); } - let futures = futures::stream::iter(set).buffer_unordered(parallel); + let futures = futures::stream::iter(set).buffer_unordered(args.parallel); let result = futures.collect::>().await; let (successful, errors): (Vec<_>, Vec<_>) = result diff --git a/wire/cli/src/cli.rs b/wire/cli/src/cli.rs index 9b7e3ea..265fe05 100644 --- a/wire/cli/src/cli.rs +++ b/wire/cli/src/cli.rs @@ -1,4 +1,4 @@ -use clap::{Parser, Subcommand, ValueEnum}; +use clap::{Args, Parser, Subcommand, ValueEnum}; use clap_complete::Shell; use clap_num::number_range; use clap_verbosity_flag::WarnLevel; @@ -70,28 +70,31 @@ fn more_than_zero(s: &str) -> Result { number_range(s, 1, usize::MAX) } -#[derive(Subcommand)] -pub enum Commands { - /// Deploy nodes - Apply { - #[arg(value_enum, default_value_t)] - goal: Goal, +#[derive(Args)] +pub struct ApplyArgs { + #[arg(value_enum, default_value_t)] + pub goal: Goal, - /// List of literal node names or `@` prefixed tags. - #[arg(short, long, value_name = "NODE | @TAG", num_args = 1..)] - on: Vec, + /// List of literal node names or `@` prefixed tags. + #[arg(short, long, value_name = "NODE | @TAG", num_args = 1..)] + pub on: Vec, - #[arg(short, long, default_value_t = 10, value_parser=more_than_zero)] - parallel: usize, + #[arg(short, long, default_value_t = 10, value_parser=more_than_zero)] + pub parallel: usize, - /// Skip key uploads. noop when [GOAL] = Keys - #[arg(short, long, default_value_t = false)] - no_keys: bool, + /// Skip key uploads. noop when [GOAL] = Keys + #[arg(short, long, default_value_t = false)] + pub no_keys: bool, - /// Overrides deployment.buildOnTarget. - #[arg(short, long, value_name = "NODE")] - always_build_local: Vec, - }, + /// Overrides deployment.buildOnTarget. + #[arg(short, long, value_name = "NODE")] + pub always_build_local: Vec, +} + +#[derive(Subcommand)] +pub enum Commands { + /// Deploy nodes + Apply(ApplyArgs), /// Inspect hive #[clap(visible_alias = "show")] Inspect { diff --git a/wire/cli/src/main.rs b/wire/cli/src/main.rs index 0ad3720..80b6b30 100644 --- a/wire/cli/src/main.rs +++ b/wire/cli/src/main.rs @@ -42,24 +42,9 @@ async fn main() -> Result<(), anyhow::Error> { } match args.command { - cli::Commands::Apply { - goal, - on, - parallel, - no_keys, - always_build_local, - } => { + cli::Commands::Apply(apply_args) => { let mut hive = Hive::new_from_path(args.path.as_path(), modifiers).await?; - apply::apply( - &mut hive, - goal.try_into()?, - on, - parallel, - no_keys, - always_build_local, - modifiers, - ) - .await?; + apply::apply(&mut hive, apply_args, args.path, modifiers).await?; } cli::Commands::Inspect { online: _, json } => println!("{}", { let hive = Hive::new_from_path(args.path.as_path(), modifiers).await?; diff --git a/wire/lib/src/hive/mod.rs b/wire/lib/src/hive/mod.rs index b43b3ba..7a085e4 100644 --- a/wire/lib/src/hive/mod.rs +++ b/wire/lib/src/hive/mod.rs @@ -1,21 +1,24 @@ use node::{Name, Node}; +use serde::de::Error; +use serde::{Deserialize, Deserializer, Serialize}; use std::collections::HashMap; use std::collections::hash_map::OccupiedEntry; use std::path::{Path, PathBuf}; use std::sync::Arc; use tracing::{debug, error, info, instrument, trace}; -use serde::{Deserialize, Serialize}; - use crate::nix::{EvalGoal, get_eval_command}; use crate::{HiveLibError, SubCommandModifiers}; pub mod node; pub mod steps; #[derive(Serialize, Deserialize, Debug, PartialEq)] +#[serde(deny_unknown_fields)] pub struct Hive { pub nodes: HashMap, - pub path: PathBuf, + + #[serde(deserialize_with = "check_schema_version", rename = "_schema")] + pub schema: u32, } pub enum Action<'a> { @@ -23,17 +26,27 @@ pub enum Action<'a> { EvaluateNode(OccupiedEntry<'a, String, Node>), } +fn check_schema_version<'de, D: Deserializer<'de>>(d: D) -> Result { + let version = u32::deserialize(d)?; + if version != Hive::SCHEMA_VERSION { + return Err(D::Error::custom( + "Version mismatch for Hive. Please ensure the binary and your wire input match!", + )); + } + Ok(version) +} + impl Hive { + const SCHEMA_VERSION: u32 = 0; + #[instrument] pub async fn new_from_path( path: &Path, modifiers: SubCommandModifiers, ) -> Result { info!("Searching upwards for hive in {}", path.display()); - let filepath = find_hive(path).ok_or(HiveLibError::NoHiveFound(path.to_path_buf()))?; - info!("Using hive {}", filepath.display()); - let command = get_eval_command(&filepath, &EvalGoal::Inspect, modifiers) + let command = get_eval_command(path, &EvalGoal::Inspect, modifiers)? .output() .await .map_err(HiveLibError::NixExecError)?; @@ -75,7 +88,7 @@ impl Hive { } } -fn find_hive(path: &Path) -> Option { +pub fn find_hive(path: &Path) -> Option { trace!("Searching for hive in {}", path.display()); let filepath_flake = path.join("flake.nix"); @@ -137,7 +150,13 @@ mod tests { path.push("hive.nix"); - assert_eq!(hive, Hive { nodes, path }); + assert_eq!( + hive, + Hive { + nodes, + schema: Hive::SCHEMA_VERSION + } + ); } #[tokio::test] @@ -169,7 +188,13 @@ mod tests { path.push("hive.nix"); - assert_eq!(hive, Hive { nodes, path }); + assert_eq!( + hive, + Hive { + nodes, + schema: Hive::SCHEMA_VERSION + } + ); } #[tokio::test] @@ -192,7 +217,13 @@ mod tests { let mut path = tmp_dir.path().to_path_buf(); path.push("flake.nix"); - assert_eq!(hive, Hive { nodes, path }); + assert_eq!( + hive, + Hive { + nodes, + schema: Hive::SCHEMA_VERSION + } + ); tmp_dir.close().unwrap(); } diff --git a/wire/lib/src/hive/node.rs b/wire/lib/src/hive/node.rs index e52e831..3351a67 100644 --- a/wire/lib/src/hive/node.rs +++ b/wire/lib/src/hive/node.rs @@ -272,6 +272,12 @@ mod tests { path.push("hive.nix"); - assert_eq!(hive, Hive { nodes, path }); + assert_eq!( + hive, + Hive { + nodes, + schema: Hive::SCHEMA_VERSION + } + ); } } diff --git a/wire/lib/src/hive/steps/evaluate.rs b/wire/lib/src/hive/steps/evaluate.rs index 9aaaa23..8cbab5c 100644 --- a/wire/lib/src/hive/steps/evaluate.rs +++ b/wire/lib/src/hive/steps/evaluate.rs @@ -25,13 +25,13 @@ impl ExecuteStep for Step { #[instrument(skip_all, name = "eval")] async fn execute(&self, ctx: &mut Context<'_>) -> Result<(), HiveLibError> { - let mut command = get_eval_command( + let command = get_eval_command( &ctx.hivepath, &EvalGoal::GetTopLevel(ctx.name), ctx.modifiers, ); - let (status, stdout_vec, stderr) = command.execute(true).in_current_span().await?; + let (status, stdout_vec, stderr) = command?.execute(true).in_current_span().await?; if status.success() { let stdout: Vec = stdout_vec diff --git a/wire/lib/src/lib.rs b/wire/lib/src/lib.rs index fb7acb9..6c3581e 100644 --- a/wire/lib/src/lib.rs +++ b/wire/lib/src/lib.rs @@ -58,7 +58,7 @@ pub enum HiveLibError { #[error("failed to execute nix command")] NixExecError(#[source] tokio::io::Error), - #[error("failed to evaluate nix expression (last 20 lines):\n{}", format_error_lines(.0))] + #[error("failed to evaluate your hive! is it valid? (last 20 lines):\n{}", format_error_lines(.0))] NixEvalError(Vec), #[error( diff --git a/wire/lib/src/nix.rs b/wire/lib/src/nix.rs index 514575f..774ac76 100644 --- a/wire/lib/src/nix.rs +++ b/wire/lib/src/nix.rs @@ -1,5 +1,4 @@ use regex::Regex; -use std::env; use std::path::Path; use std::process::{Command, ExitStatus}; use std::sync::LazyLock; @@ -8,6 +7,7 @@ use tokio::io::{AsyncBufReadExt, AsyncRead}; use tracing::{Instrument, Span, error, info, trace}; use tracing_indicatif::span_ext::IndicatifSpanExt; +use crate::hive::find_hive; use crate::hive::node::Name; use crate::nix_log::{Action, Internal, NixLog, Trace}; use crate::{HiveLibError, SubCommandModifiers}; @@ -44,60 +44,42 @@ pub fn get_eval_command( path: &Path, goal: &EvalGoal, modifiers: SubCommandModifiers, -) -> tokio::process::Command { - let runtime = match env::var_os("WIRE_RUNTIME") { - Some(runtime) => runtime.into_string().unwrap(), - None => panic!("WIRE_RUNTIME environment variable not set"), - }; - +) -> Result { assert!(check_nix_available(), "nix is not available on this system"); - let canon_path = path.canonicalize().unwrap(); + let canon_path = find_hive(&path.canonicalize().unwrap()) + .ok_or(HiveLibError::NoHiveFound(path.to_path_buf()))?; let mut command = tokio::process::Command::new("nix"); command.args(["--extra-experimental-features", "nix-command"]); command.args(["--extra-experimental-features", "flakes"]); - command.args(["eval", "--json", "--impure"]); + command.args(["eval", "--json"]); + if modifiers.show_trace { command.arg("--show-trace"); } - command.args(["--expr"]); - - command.arg(format!( - "let flake = {flake}; evaluate = import {runtime}/evaluate.nix; hive = evaluate {{hive = \ - {hive}; path = {path}; nixosConfigurations = {nixosConfigurations}; nixpkgs = \ - {nixpkgs};}}; in {goal}", - flake = if canon_path.ends_with("flake.nix") { - format!( - "(builtins.getFlake \"git+file://{path}\")", - path = canon_path.parent().unwrap().to_str().unwrap(), - ) - } else { - "null".to_string() - }, - hive = if canon_path.ends_with("flake.nix") { - "flake.colmena".to_string() - } else { - format!("import {path}", path = canon_path.to_str().unwrap()) - }, - nixosConfigurations = if canon_path.ends_with("flake.nix") { - "flake.nixosConfigurations or {}".to_string() - } else { - "{}".to_string() - }, - nixpkgs = if canon_path.ends_with("flake.nix") { - "flake.inputs.nixpkgs.outPath or null".to_string() - } else { - "null".to_string() - }, - path = canon_path.to_str().unwrap(), - goal = match goal { - EvalGoal::Inspect => "hive.inspect".to_string(), - EvalGoal::GetTopLevel(node) => format!("hive.getTopLevel \"{node}\""), - } - )); - command + if canon_path.ends_with("flake.nix") { + command.arg(format!("{}#colmena", canon_path.to_str().unwrap())); + command.arg("--apply"); + + command.arg(format!( + "hive: {goal}", + goal = match goal { + EvalGoal::Inspect => "hive.inspect".to_string(), + EvalGoal::GetTopLevel(node) => format!("hive.topLevels.{node}"), + } + )); + } else { + command.args(["--file", &canon_path.to_string_lossy()]); + + command.arg(match goal { + EvalGoal::Inspect => "inspect".to_string(), + EvalGoal::GetTopLevel(node) => format!("topLevels.{node}"), + }); + } + + Ok(command) } pub async fn handle_io(reader: R, should_trace: bool) -> Result, HiveLibError> diff --git a/wire/lib/src/test_support.rs b/wire/lib/src/test_support.rs index fc8c43f..6d44feb 100644 --- a/wire/lib/src/test_support.rs +++ b/wire/lib/src/test_support.rs @@ -16,6 +16,25 @@ pub fn make_flake_sandbox(path: &Path) -> Result { fs::copy(entry.path(), tmp_dir.as_ref().join(entry.file_name()))?; } + let root = path.parent().unwrap().parent().unwrap().parent().unwrap(); + + fs::copy( + root.join(Path::new("runtime/evaluate.nix")), + tmp_dir.as_ref().join("evaluate.nix"), + )?; + fs::copy( + root.join(Path::new("runtime/module.nix")), + tmp_dir.as_ref().join("module.nix"), + )?; + fs::copy( + root.join(Path::new("runtime/makeHive.nix")), + tmp_dir.as_ref().join("makeHive.nix"), + )?; + fs::copy( + root.join(Path::new("flake.lock")), + tmp_dir.as_ref().join("flake.lock"), + )?; + Command::new("git") .args(["add", "-A"]) .current_dir(tmp_dir.path()) -- 2.51.2