ALPHA: wire is a tool to deploy nixos systems wire.althaea.zone/

let --path accept flakeref (#307)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

authored by

marshmallow
autofix-ci[bot]
and committed by
GitHub
c6829260 aed25b23

+463 -112
+3
CHANGELOG.md
··· 11 11 12 12 - `--ssh-accept-host` was added. 13 13 - `--on -` will now read additional apply targets from stdin. 14 + - `--path` now supports flakerefs (`github:foo/bar`, `git+file:///...`, 15 + `gitlab:foo/bar`, etc). 16 + - `--flake` is now an alias for `--path`. 14 17 15 18 ### Fixed 16 19
+281 -2
Cargo.lock
··· 458 458 ] 459 459 460 460 [[package]] 461 + name = "displaydoc" 462 + version = "0.2.5" 463 + source = "registry+https://github.com/rust-lang/crates.io-index" 464 + checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 465 + dependencies = [ 466 + "proc-macro2", 467 + "quote", 468 + "syn 2.0.106", 469 + ] 470 + 471 + [[package]] 461 472 name = "downcast-rs" 462 473 version = "1.2.1" 463 474 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 578 589 version = "1.0.7" 579 590 source = "registry+https://github.com/rust-lang/crates.io-index" 580 591 checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 592 + 593 + [[package]] 594 + name = "form_urlencoded" 595 + version = "1.2.2" 596 + source = "registry+https://github.com/rust-lang/crates.io-index" 597 + checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" 598 + dependencies = [ 599 + "percent-encoding", 600 + ] 581 601 582 602 [[package]] 583 603 name = "fuchsia-cprng" ··· 736 756 checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 737 757 738 758 [[package]] 759 + name = "icu_collections" 760 + version = "2.0.0" 761 + source = "registry+https://github.com/rust-lang/crates.io-index" 762 + checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" 763 + dependencies = [ 764 + "displaydoc", 765 + "potential_utf", 766 + "yoke", 767 + "zerofrom", 768 + "zerovec", 769 + ] 770 + 771 + [[package]] 772 + name = "icu_locale_core" 773 + version = "2.0.0" 774 + source = "registry+https://github.com/rust-lang/crates.io-index" 775 + checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" 776 + dependencies = [ 777 + "displaydoc", 778 + "litemap", 779 + "tinystr", 780 + "writeable", 781 + "zerovec", 782 + ] 783 + 784 + [[package]] 785 + name = "icu_normalizer" 786 + version = "2.0.0" 787 + source = "registry+https://github.com/rust-lang/crates.io-index" 788 + checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" 789 + dependencies = [ 790 + "displaydoc", 791 + "icu_collections", 792 + "icu_normalizer_data", 793 + "icu_properties", 794 + "icu_provider", 795 + "smallvec", 796 + "zerovec", 797 + ] 798 + 799 + [[package]] 800 + name = "icu_normalizer_data" 801 + version = "2.0.0" 802 + source = "registry+https://github.com/rust-lang/crates.io-index" 803 + checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" 804 + 805 + [[package]] 806 + name = "icu_properties" 807 + version = "2.0.1" 808 + source = "registry+https://github.com/rust-lang/crates.io-index" 809 + checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" 810 + dependencies = [ 811 + "displaydoc", 812 + "icu_collections", 813 + "icu_locale_core", 814 + "icu_properties_data", 815 + "icu_provider", 816 + "potential_utf", 817 + "zerotrie", 818 + "zerovec", 819 + ] 820 + 821 + [[package]] 822 + name = "icu_properties_data" 823 + version = "2.0.1" 824 + source = "registry+https://github.com/rust-lang/crates.io-index" 825 + checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" 826 + 827 + [[package]] 828 + name = "icu_provider" 829 + version = "2.0.0" 830 + source = "registry+https://github.com/rust-lang/crates.io-index" 831 + checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" 832 + dependencies = [ 833 + "displaydoc", 834 + "icu_locale_core", 835 + "stable_deref_trait", 836 + "tinystr", 837 + "writeable", 838 + "yoke", 839 + "zerofrom", 840 + "zerotrie", 841 + "zerovec", 842 + ] 843 + 844 + [[package]] 739 845 name = "ident_case" 740 846 version = "1.0.1" 741 847 source = "registry+https://github.com/rust-lang/crates.io-index" 742 848 checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 743 849 744 850 [[package]] 851 + name = "idna" 852 + version = "1.1.0" 853 + source = "registry+https://github.com/rust-lang/crates.io-index" 854 + checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" 855 + dependencies = [ 856 + "idna_adapter", 857 + "smallvec", 858 + "utf8_iter", 859 + ] 860 + 861 + [[package]] 862 + name = "idna_adapter" 863 + version = "1.2.1" 864 + source = "registry+https://github.com/rust-lang/crates.io-index" 865 + checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" 866 + dependencies = [ 867 + "icu_normalizer", 868 + "icu_properties", 869 + ] 870 + 871 + [[package]] 745 872 name = "im" 746 873 version = "15.1.0" 747 874 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 895 1022 checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" 896 1023 897 1024 [[package]] 1025 + name = "litemap" 1026 + version = "0.8.0" 1027 + source = "registry+https://github.com/rust-lang/crates.io-index" 1028 + checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" 1029 + 1030 + [[package]] 898 1031 name = "lock_api" 899 1032 version = "0.4.13" 900 1033 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1037 1170 [[package]] 1038 1171 name = "nix-compat" 1039 1172 version = "0.1.0" 1040 - source = "git+https://git.snix.dev/snix/snix.git#d89202f5a2db180474deb31b717c2554a6004895" 1173 + source = "git+https://git.snix.dev/snix/snix.git#4aaef4cdf6f7766eedcfe1b5bad8f1c4e4d05c12" 1041 1174 dependencies = [ 1042 1175 "bitflags 2.9.1", 1043 1176 "bstr", ··· 1058 1191 "thiserror 2.0.17", 1059 1192 "tokio", 1060 1193 "tracing", 1194 + "url", 1061 1195 ] 1062 1196 1063 1197 [[package]] 1064 1198 name = "nix-compat-derive" 1065 1199 version = "0.1.0" 1066 - source = "git+https://git.snix.dev/snix/snix.git#d89202f5a2db180474deb31b717c2554a6004895" 1200 + source = "git+https://git.snix.dev/snix/snix.git#4aaef4cdf6f7766eedcfe1b5bad8f1c4e4d05c12" 1067 1201 dependencies = [ 1068 1202 "proc-macro2", 1069 1203 "quote", ··· 1170 1304 ] 1171 1305 1172 1306 [[package]] 1307 + name = "percent-encoding" 1308 + version = "2.3.2" 1309 + source = "registry+https://github.com/rust-lang/crates.io-index" 1310 + checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" 1311 + 1312 + [[package]] 1173 1313 name = "petgraph" 1174 1314 version = "0.7.1" 1175 1315 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1226 1366 "shell-words", 1227 1367 "winapi", 1228 1368 "winreg", 1369 + ] 1370 + 1371 + [[package]] 1372 + name = "potential_utf" 1373 + version = "0.1.3" 1374 + source = "registry+https://github.com/rust-lang/crates.io-index" 1375 + checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" 1376 + dependencies = [ 1377 + "zerovec", 1229 1378 ] 1230 1379 1231 1380 [[package]] ··· 1765 1914 ] 1766 1915 1767 1916 [[package]] 1917 + name = "stable_deref_trait" 1918 + version = "1.2.1" 1919 + source = "registry+https://github.com/rust-lang/crates.io-index" 1920 + checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" 1921 + 1922 + [[package]] 1768 1923 name = "strsim" 1769 1924 version = "0.11.1" 1770 1925 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1817 1972 "proc-macro2", 1818 1973 "quote", 1819 1974 "unicode-ident", 1975 + ] 1976 + 1977 + [[package]] 1978 + name = "synstructure" 1979 + version = "0.13.2" 1980 + source = "registry+https://github.com/rust-lang/crates.io-index" 1981 + checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" 1982 + dependencies = [ 1983 + "proc-macro2", 1984 + "quote", 1985 + "syn 2.0.106", 1820 1986 ] 1821 1987 1822 1988 [[package]] ··· 1918 2084 ] 1919 2085 1920 2086 [[package]] 2087 + name = "tinystr" 2088 + version = "0.8.1" 2089 + source = "registry+https://github.com/rust-lang/crates.io-index" 2090 + checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" 2091 + dependencies = [ 2092 + "displaydoc", 2093 + "zerovec", 2094 + ] 2095 + 2096 + [[package]] 1921 2097 name = "tokio" 1922 2098 version = "1.48.0" 1923 2099 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2086 2262 version = "0.5.1" 2087 2263 source = "registry+https://github.com/rust-lang/crates.io-index" 2088 2264 checksum = "323402cff2dd658f39ca17c789b502021b3f18707c91cdf22e3838e1b4023817" 2265 + 2266 + [[package]] 2267 + name = "url" 2268 + version = "2.5.7" 2269 + source = "registry+https://github.com/rust-lang/crates.io-index" 2270 + checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" 2271 + dependencies = [ 2272 + "form_urlencoded", 2273 + "idna", 2274 + "percent-encoding", 2275 + "serde", 2276 + ] 2277 + 2278 + [[package]] 2279 + name = "utf8_iter" 2280 + version = "1.0.4" 2281 + source = "registry+https://github.com/rust-lang/crates.io-index" 2282 + checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 2089 2283 2090 2284 [[package]] 2091 2285 name = "utf8parse" ··· 2432 2626 "itertools", 2433 2627 "lib", 2434 2628 "miette", 2629 + "nix-compat", 2435 2630 "serde", 2436 2631 "serde_json", 2437 2632 "thiserror 2.0.17", ··· 2451 2646 ] 2452 2647 2453 2648 [[package]] 2649 + name = "writeable" 2650 + version = "0.6.1" 2651 + source = "registry+https://github.com/rust-lang/crates.io-index" 2652 + checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" 2653 + 2654 + [[package]] 2655 + name = "yoke" 2656 + version = "0.8.0" 2657 + source = "registry+https://github.com/rust-lang/crates.io-index" 2658 + checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" 2659 + dependencies = [ 2660 + "serde", 2661 + "stable_deref_trait", 2662 + "yoke-derive", 2663 + "zerofrom", 2664 + ] 2665 + 2666 + [[package]] 2667 + name = "yoke-derive" 2668 + version = "0.8.0" 2669 + source = "registry+https://github.com/rust-lang/crates.io-index" 2670 + checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" 2671 + dependencies = [ 2672 + "proc-macro2", 2673 + "quote", 2674 + "syn 2.0.106", 2675 + "synstructure", 2676 + ] 2677 + 2678 + [[package]] 2454 2679 name = "zerocopy" 2455 2680 version = "0.8.26" 2456 2681 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2471 2696 ] 2472 2697 2473 2698 [[package]] 2699 + name = "zerofrom" 2700 + version = "0.1.6" 2701 + source = "registry+https://github.com/rust-lang/crates.io-index" 2702 + checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 2703 + dependencies = [ 2704 + "zerofrom-derive", 2705 + ] 2706 + 2707 + [[package]] 2708 + name = "zerofrom-derive" 2709 + version = "0.1.6" 2710 + source = "registry+https://github.com/rust-lang/crates.io-index" 2711 + checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" 2712 + dependencies = [ 2713 + "proc-macro2", 2714 + "quote", 2715 + "syn 2.0.106", 2716 + "synstructure", 2717 + ] 2718 + 2719 + [[package]] 2474 2720 name = "zeroize" 2475 2721 version = "1.8.2" 2476 2722 source = "registry+https://github.com/rust-lang/crates.io-index" 2477 2723 checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" 2724 + 2725 + [[package]] 2726 + name = "zerotrie" 2727 + version = "0.2.2" 2728 + source = "registry+https://github.com/rust-lang/crates.io-index" 2729 + checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" 2730 + dependencies = [ 2731 + "displaydoc", 2732 + "yoke", 2733 + "zerofrom", 2734 + ] 2735 + 2736 + [[package]] 2737 + name = "zerovec" 2738 + version = "0.11.4" 2739 + source = "registry+https://github.com/rust-lang/crates.io-index" 2740 + checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" 2741 + dependencies = [ 2742 + "yoke", 2743 + "zerofrom", 2744 + "zerovec-derive", 2745 + ] 2746 + 2747 + [[package]] 2748 + name = "zerovec-derive" 2749 + version = "0.11.1" 2750 + source = "registry+https://github.com/rust-lang/crates.io-index" 2751 + checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" 2752 + dependencies = [ 2753 + "proc-macro2", 2754 + "quote", 2755 + "syn 2.0.106", 2756 + ]
+4
Cargo.toml
··· 26 26 sha2 = "0.10.9" 27 27 tokio-util = { version = "0.7.16", features = ["codec"] } 28 28 base64 = "0.22.1" 29 + nix-compat = { git = "https://git.snix.dev/snix/snix.git", features = [ 30 + "serde", 31 + "flakeref", 32 + ] }
+1
wire/cli/Cargo.toml
··· 27 27 itertools = "0.14.0" 28 28 dhat = "0.3.2" 29 29 clap_complete = "4.5.59" 30 + nix-compat = { workspace = true }
+4 -6
wire/cli/src/apply.rs
··· 3 3 4 4 use futures::{FutureExt, StreamExt}; 5 5 use itertools::{Either, Itertools}; 6 - use lib::hive::Hive; 7 6 use lib::hive::node::{Context, GoalExecutor, Name, StepState}; 7 + use lib::hive::{Hive, HiveLocation}; 8 8 use lib::{SubCommandModifiers, errors::HiveLibError}; 9 9 use miette::{Diagnostic, IntoDiagnostic, Result}; 10 10 use std::collections::HashSet; 11 11 use std::io::Read; 12 - use std::path::PathBuf; 13 12 use std::sync::{Arc, Mutex}; 14 13 use thiserror::Error; 15 14 use tracing::{Span, error, info}; ··· 51 50 // #[instrument(skip_all, fields(goal = %args.goal, on = %args.on.iter().join(", ")))] 52 51 pub async fn apply( 53 52 hive: &mut Hive, 53 + location: HiveLocation, 54 54 args: ApplyArgs, 55 - path: PathBuf, 56 55 mut modifiers: SubCommandModifiers, 57 56 clobber_lock: Arc<Mutex<()>>, 58 57 ) -> Result<()> { 59 58 let header_span = Span::current(); 59 + let location = Arc::new(location); 60 60 61 61 // Respect user's --always-build-local arg 62 62 hive.force_always_local(args.always_build_local)?; ··· 95 95 || node.tags.iter().any(|tag| tags.contains(tag)) 96 96 }) 97 97 .map(|node| { 98 - let path = path.clone(); 99 - 100 98 info!("Resolved {:?} to include {}", args.on, node.0); 101 99 102 100 let context = Context { ··· 105 103 goal: args.goal.clone().try_into().unwrap(), 106 104 state: StepState::default(), 107 105 no_keys: args.no_keys, 108 - hivepath: path, 106 + hive_location: location.clone(), 109 107 modifiers, 110 108 reboot: args.reboot, 111 109 clobber_lock: clobber_lock.clone(),
+3 -3
wire/cli/src/cli.rs
··· 31 31 #[command(flatten)] 32 32 pub verbose: clap_verbosity_flag::Verbosity<WarnLevel>, 33 33 34 - /// Path to directory containing hive 35 - #[arg(long, global = true, default_value = std::env::current_dir().unwrap().into_os_string())] 36 - pub path: std::path::PathBuf, 34 + /// Path or flake reference 35 + #[arg(long, global = true, default_value = std::env::current_dir().unwrap().into_os_string(), visible_alias("flake"))] 36 + pub path: String, 37 37 38 38 // Unused until a solution to tracing-indicatif log deadlocking is found... 39 39 /// Hide progress bars.
+6 -4
wire/cli/src/main.rs
··· 14 14 use clap::Parser; 15 15 use clap_complete::generate; 16 16 use lib::hive::Hive; 17 + use lib::hive::get_hive_location; 17 18 use miette::IntoDiagnostic; 18 19 use miette::Result; 19 20 use tracing::error; ··· 51 52 miette::bail!("Nix is not availabile on this system."); 52 53 } 53 54 55 + let location = get_hive_location(args.path)?; 56 + 54 57 match args.command { 55 58 cli::Commands::Apply(apply_args) => { 56 - let mut hive = 57 - Hive::new_from_path(args.path.as_path(), modifiers, clobber_lock.clone()).await?; 58 - apply::apply(&mut hive, apply_args, args.path, modifiers, clobber_lock).await?; 59 + let mut hive = Hive::new_from_path(&location, modifiers, clobber_lock.clone()).await?; 60 + apply::apply(&mut hive, location, apply_args, modifiers, clobber_lock).await?; 59 61 } 60 62 cli::Commands::Inspect { online: _, json } => println!("{}", { 61 - let hive = Hive::new_from_path(args.path.as_path(), modifiers, clobber_lock).await?; 63 + let hive = Hive::new_from_path(&location, modifiers, clobber_lock).await?; 62 64 if json { 63 65 serde_json::to_string(&hive).into_diagnostic()? 64 66 } else {
+1 -3
wire/lib/Cargo.toml
··· 31 31 enum_dispatch = "0.3.13" 32 32 sha2 = { workspace = true } 33 33 base64 = { workspace = true } 34 - nix-compat = { git = "https://git.snix.dev/snix/snix.git", features = [ 35 - "serde", 36 - ] } 34 + nix-compat = { workspace = true } 37 35 38 36 [dev-dependencies] 39 37 tempdir = "0.3"
+24 -28
wire/lib/src/commands/common.rs
··· 3 3 4 4 use std::{ 5 5 collections::HashMap, 6 - path::Path, 7 6 sync::{Arc, Mutex}, 8 7 }; 9 8 10 9 use crate::{ 11 10 EvalGoal, SubCommandModifiers, 12 11 commands::{ChildOutputMode, CommandArguments, WireCommand, WireCommandChip, get_command}, 13 - errors::{HiveInitializationError, HiveLibError}, 12 + errors::HiveLibError, 14 13 hive::{ 15 - find_hive, 14 + HiveLocation, 16 15 node::{Name, Node, Push}, 17 16 }, 18 17 }; ··· 59 58 Ok(()) 60 59 } 61 60 62 - /// Evaluates the hive in path with regards to the given goal, 61 + /// Evaluates the hive in flakeref with regards to the given goal, 63 62 /// and returns stdout. 64 63 pub async fn evaluate_hive_attribute( 65 - path: &Path, 64 + location: &HiveLocation, 66 65 goal: &EvalGoal<'_>, 67 66 modifiers: SubCommandModifiers, 68 67 clobber_lock: Arc<Mutex<()>>, 69 68 ) -> Result<String, HiveLibError> { 70 - let canon_path = 71 - find_hive(&path.canonicalize().unwrap()).ok_or(HiveLibError::HiveInitializationError( 72 - HiveInitializationError::NoHiveFound(path.to_path_buf()), 73 - ))?; 74 - 75 69 let mut command = get_command(None, ChildOutputMode::Nix, modifiers).await?; 76 70 77 - let attribute = if canon_path.ends_with("flake.nix") { 78 - format!( 79 - "{}#wire --apply \"hive: {}\"", 80 - canon_path.to_str().unwrap(), 81 - match goal { 82 - EvalGoal::Inspect => "hive.inspect".to_string(), 83 - EvalGoal::GetTopLevel(node) => format!("hive.topLevels.{node}"), 84 - } 85 - ) 86 - } else { 87 - format!( 88 - "--file {} {}", 89 - &canon_path.to_string_lossy(), 90 - match goal { 91 - EvalGoal::Inspect => "inspect".to_string(), 92 - EvalGoal::GetTopLevel(node) => format!("topLevels.{node}"), 93 - } 94 - ) 71 + let attribute = match location { 72 + HiveLocation::Flake(uri) => { 73 + format!( 74 + "{uri}#wire --apply \"hive: {}\"", 75 + match goal { 76 + EvalGoal::Inspect => "hive.inspect".to_string(), 77 + EvalGoal::GetTopLevel(node) => format!("hive.topLevels.{node}"), 78 + } 79 + ) 80 + } 81 + HiveLocation::HiveNix(path) => { 82 + format!( 83 + "--file {} {}", 84 + &path.to_string_lossy(), 85 + match goal { 86 + EvalGoal::Inspect => "inspect".to_string(), 87 + EvalGoal::GetTopLevel(node) => format!("topLevels.{node}"), 88 + } 89 + ) 90 + } 95 91 }; 96 92 97 93 let command_string = format!(
+29
wire/lib/src/errors.rs
··· 6 6 use std::{num::ParseIntError, path::PathBuf, process::ExitStatus, sync::mpsc::RecvError}; 7 7 8 8 use miette::{Diagnostic, SourceSpan}; 9 + use nix_compat::flakeref::{FlakeRef, FlakeRefError}; 9 10 use thiserror::Error; 10 11 use tokio::task::JoinError; 11 12 ··· 149 150 } 150 151 151 152 #[derive(Debug, Diagnostic, Error)] 153 + pub enum HiveLocationError { 154 + #[diagnostic( 155 + code(wire::hive_location::MalformedPath), 156 + url("{DOCS_URL}#{}", self.code().unwrap()) 157 + )] 158 + #[error("Path was malformed: {}", .0.display())] 159 + MalformedPath(PathBuf), 160 + 161 + #[diagnostic( 162 + code(wire::hive_location::Malformed), 163 + url("{DOCS_URL}#{}", self.code().unwrap()) 164 + )] 165 + #[error("--path was malformed")] 166 + Malformed(#[source] FlakeRefError), 167 + 168 + #[diagnostic( 169 + code(wire::hive_location::TypeUnsupported), 170 + url("{DOCS_URL}#{}", self.code().unwrap()) 171 + )] 172 + #[error("The flakref had an unsupported type: {:#?}", .0)] 173 + TypeUnsupported(FlakeRef), 174 + } 175 + 176 + #[derive(Debug, Diagnostic, Error)] 152 177 pub enum CommandError { 153 178 #[diagnostic( 154 179 code(wire::command::TermAttrs), ··· 256 281 #[error(transparent)] 257 282 #[diagnostic(transparent)] 258 283 CommandError(CommandError), 284 + 285 + #[error(transparent)] 286 + #[diagnostic(transparent)] 287 + HiveLocationError(HiveLocationError), 259 288 260 289 #[error("Failed to apply key {}", .0)] 261 290 KeyError(
+71 -47
wire/lib/src/hive/mod.rs
··· 1 1 // SPDX-License-Identifier: AGPL-3.0-or-later 2 2 // Copyright 2024-2025 wire Contributors 3 3 4 + use nix_compat::flakeref::FlakeRef; 4 5 use node::{Name, Node}; 5 6 use serde::de::Error; 6 7 use serde::{Deserialize, Deserializer, Serialize}; 7 8 use std::collections::HashMap; 8 9 use std::collections::hash_map::OccupiedEntry; 9 - use std::path::{Path, PathBuf}; 10 + use std::ffi::OsStr; 11 + use std::fs; 12 + use std::path::PathBuf; 13 + use std::str::FromStr; 10 14 use std::sync::{Arc, Mutex}; 11 - use tracing::{error, info, instrument, trace}; 15 + use tracing::{info, instrument}; 12 16 13 17 use crate::commands::common::evaluate_hive_attribute; 14 - use crate::errors::HiveInitializationError; 18 + use crate::errors::{HiveInitializationError, HiveLocationError}; 15 19 use crate::{EvalGoal, HiveLibError, SubCommandModifiers}; 16 20 pub mod node; 17 21 pub mod steps; ··· 45 49 46 50 #[instrument(skip_all, name = "eval_hive")] 47 51 pub async fn new_from_path( 48 - path: &Path, 52 + location: &HiveLocation, 49 53 modifiers: SubCommandModifiers, 50 54 clobber_lock: Arc<Mutex<()>>, 51 55 ) -> Result<Hive, HiveLibError> { 52 - info!("Searching upwards for hive in {}", path.display()); 53 - 54 56 let output = 55 - evaluate_hive_attribute(path, &EvalGoal::Inspect, modifiers, clobber_lock).await?; 57 + evaluate_hive_attribute(location, &EvalGoal::Inspect, modifiers, clobber_lock).await?; 56 58 57 59 info!("evaluate_hive_attribute ouputted {output}"); 58 60 ··· 82 84 } 83 85 } 84 86 85 - pub fn find_hive(path: &Path) -> Option<PathBuf> { 86 - trace!("Searching for hive in {}", path.display()); 87 - let filepath_flake = path.join("flake.nix"); 87 + #[derive(Debug, PartialEq, Eq)] 88 + pub enum HiveLocation { 89 + HiveNix(PathBuf), 90 + Flake(String), 91 + } 88 92 89 - if filepath_flake.is_file() { 90 - return Some(filepath_flake); 91 - } 92 - let filepath_hive = path.join("hive.nix"); 93 + pub fn get_hive_location(path: String) -> Result<HiveLocation, HiveLocationError> { 94 + let flakeref = FlakeRef::from_str(&path); 93 95 94 - if filepath_hive.is_file() { 95 - return Some(filepath_hive); 96 - } 96 + let path_to_location = |path: PathBuf| { 97 + Ok(match path.file_name().and_then(OsStr::to_str) { 98 + Some("hive.nix") => HiveLocation::HiveNix(path.clone()), 99 + Some(_) => { 100 + if fs::metadata(path.join("flake.nix")).is_ok() { 101 + HiveLocation::Flake(path.join("flake.nix").display().to_string()) 102 + } else { 103 + HiveLocation::HiveNix(path.join("hive.nix")) 104 + } 105 + } 106 + None => return Err(HiveLocationError::MalformedPath(path.clone())), 107 + }) 108 + }; 97 109 98 - if let Some(parent) = path.parent() { 99 - return find_hive(parent); 110 + match flakeref { 111 + Err(nix_compat::flakeref::FlakeRefError::UrlParseError(_err)) => { 112 + let path = PathBuf::from(path); 113 + Ok(path_to_location(path)?) 114 + } 115 + Ok(FlakeRef::Path { path, .. }) => Ok(path_to_location(path)?), 116 + Ok( 117 + FlakeRef::Git { .. } 118 + | FlakeRef::GitHub { .. } 119 + | FlakeRef::GitLab { .. } 120 + | FlakeRef::Tarball { .. } 121 + | FlakeRef::Mercurial { .. } 122 + | FlakeRef::SourceHut { .. }, 123 + ) => Ok(HiveLocation::Flake(path)), 124 + Err(err) => Err(HiveLocationError::Malformed(err)), 125 + Ok(flakeref) => Err(HiveLocationError::TypeUnsupported(flakeref)), 100 126 } 101 - 102 - error!("No hive found"); 103 - None 104 127 } 105 128 106 129 #[cfg(test)] ··· 111 134 errors::CommandError, 112 135 get_test_path, 113 136 hive::steps::keys::{Key, Source, UploadKeyAt}, 137 + location, 114 138 test_support::{get_clobber_lock, make_flake_sandbox}, 115 139 }; 116 140 117 141 use super::*; 118 142 use std::{assert_matches::assert_matches, env}; 119 143 144 + // flake should always come before hive.nix 120 145 #[test] 121 146 fn test_hive_dot_nix_priority() { 122 - let path = get_test_path!(); 147 + let location = location!(get_test_path!()); 123 148 124 - let hive = find_hive(&path).unwrap(); 125 - 126 - assert!(hive.ends_with("flake.nix")); 149 + assert_matches!(location, HiveLocation::Flake(..)); 127 150 } 128 151 129 152 #[tokio::test] 130 153 #[cfg_attr(feature = "no_web_tests", ignore)] 131 154 async fn test_hive_file() { 132 - let mut path = get_test_path!(); 155 + let location = location!(get_test_path!()); 133 156 134 - let hive = Hive::new_from_path(&path, SubCommandModifiers::default(), get_clobber_lock()) 135 - .await 136 - .unwrap(); 157 + let hive = Hive::new_from_path( 158 + &location, 159 + SubCommandModifiers::default(), 160 + get_clobber_lock(), 161 + ) 162 + .await 163 + .unwrap(); 137 164 138 165 let node = Node { 139 166 target: node::Target::from_host("192.168.122.96"), ··· 142 169 143 170 let mut nodes = HashMap::new(); 144 171 nodes.insert(Name("node-a".into()), node); 145 - 146 - path.push("hive.nix"); 147 172 148 173 assert_eq!( 149 174 hive, ··· 157 182 #[tokio::test] 158 183 #[cfg_attr(feature = "no_web_tests", ignore)] 159 184 async fn non_trivial_hive() { 160 - let mut path = get_test_path!(); 185 + let location = location!(get_test_path!()); 161 186 162 - let hive = Hive::new_from_path(&path, SubCommandModifiers::default(), get_clobber_lock()) 163 - .await 164 - .unwrap(); 187 + let hive = Hive::new_from_path( 188 + &location, 189 + SubCommandModifiers::default(), 190 + get_clobber_lock(), 191 + ) 192 + .await 193 + .unwrap(); 165 194 166 195 let node = Node { 167 196 target: node::Target::from_host("name"), ··· 182 211 let mut nodes = HashMap::new(); 183 212 nodes.insert(Name("node-a".into()), node); 184 213 185 - path.push("hive.nix"); 186 - 187 214 assert_eq!( 188 215 hive, 189 216 Hive { ··· 198 225 async fn flake_hive() { 199 226 let tmp_dir = make_flake_sandbox(&get_test_path!()).unwrap(); 200 227 228 + let location = get_hive_location(tmp_dir.path().display().to_string()).unwrap(); 201 229 let hive = Hive::new_from_path( 202 - tmp_dir.path(), 230 + &location, 203 231 SubCommandModifiers::default(), 204 232 get_clobber_lock(), 205 233 ) ··· 212 240 nodes.insert(Name("node-a".into()), Node::from_host("node-a")); 213 241 // a non-merged node 214 242 nodes.insert(Name("node-b".into()), Node::from_host("node-b")); 215 - // omit a node called system-c 216 - 217 - let mut path = tmp_dir.path().to_path_buf(); 218 - path.push("flake.nix"); 219 243 220 244 assert_eq!( 221 245 hive, ··· 230 254 231 255 #[tokio::test] 232 256 async fn no_nixpkgs() { 233 - let path = get_test_path!(); 257 + let location = location!(get_test_path!()); 234 258 235 259 assert_matches!( 236 - Hive::new_from_path(&path, SubCommandModifiers::default(), get_clobber_lock()).await, 260 + Hive::new_from_path(&location, SubCommandModifiers::default(), get_clobber_lock()).await, 237 261 Err(HiveLibError::NixEvalError { 238 262 source: CommandError::CommandFailed { 239 263 logs, ··· 247 271 248 272 #[tokio::test] 249 273 async fn _keys_should_fail() { 250 - let path = get_test_path!(); 274 + let location = location!(get_test_path!()); 251 275 252 276 assert_matches!( 253 - Hive::new_from_path(&path, SubCommandModifiers::default(), get_clobber_lock()).await, 277 + Hive::new_from_path(&location, SubCommandModifiers::default(), get_clobber_lock()).await, 254 278 Err(HiveLibError::NixEvalError { 255 279 source: CommandError::CommandFailed { 256 280 logs,
+29 -18
wire/lib/src/hive/node.rs
··· 7 7 use serde::{Deserialize, Serialize}; 8 8 use std::collections::HashMap; 9 9 use std::fmt::Display; 10 - use std::path::PathBuf; 11 10 use std::sync::{Arc, Mutex}; 12 11 use tracing::{Level, error, event, instrument, trace}; 13 12 ··· 16 15 ChildOutputMode, CommandArguments, WireCommand, WireCommandChip, get_command, 17 16 }; 18 17 use crate::errors::NetworkError; 18 + use crate::hive::HiveLocation; 19 19 use crate::hive::steps::build::Build; 20 20 use crate::hive::steps::evaluate::Evaluate; 21 21 use crate::hive::steps::keys::{Key, Keys, PushKeyAgent, UploadKeyAt}; ··· 110 110 #[cfg(test)] 111 111 impl<'a> Context<'a> { 112 112 fn create_test_context( 113 - hivepath: std::path::PathBuf, 113 + hive_location: HiveLocation, 114 114 name: &'a Name, 115 115 node: &'a mut Node, 116 116 ) -> Self { ··· 119 119 Context { 120 120 name, 121 121 node, 122 - hivepath, 122 + hive_location: Arc::new(hive_location), 123 123 modifiers: SubCommandModifiers::default(), 124 124 no_keys: false, 125 125 state: StepState::default(), ··· 281 281 pub struct Context<'a> { 282 282 pub name: &'a Name, 283 283 pub node: &'a mut Node, 284 - pub hivepath: PathBuf, 284 + pub hive_location: Arc<HiveLocation>, 285 285 pub modifiers: SubCommandModifiers, 286 286 pub no_keys: bool, 287 287 pub state: StepState, ··· 379 379 #[cfg(test)] 380 380 mod tests { 381 381 use super::*; 382 - use crate::{function_name, get_test_path, hive::Hive, test_support::get_clobber_lock}; 382 + use crate::{ 383 + function_name, get_test_path, 384 + hive::{Hive, get_hive_location}, 385 + location, 386 + test_support::get_clobber_lock, 387 + }; 388 + use std::path::PathBuf; 383 389 use std::{collections::HashMap, env}; 384 390 385 391 fn get_steps(goal_executor: GoalExecutor) -> std::vec::Vec<Step> { ··· 395 401 async fn default_values_match() { 396 402 let mut path = get_test_path!(); 397 403 398 - let hive = Hive::new_from_path(&path, SubCommandModifiers::default(), get_clobber_lock()) 399 - .await 400 - .unwrap(); 404 + let location = get_hive_location(path.display().to_string()).unwrap(); 405 + let hive = Hive::new_from_path( 406 + &location, 407 + SubCommandModifiers::default(), 408 + get_clobber_lock(), 409 + ) 410 + .await 411 + .unwrap(); 401 412 402 413 let node = Node::default(); 403 414 ··· 417 428 418 429 #[tokio::test] 419 430 async fn order_build_locally() { 420 - let path = get_test_path!(); 431 + let location = location!(get_test_path!()); 421 432 let mut node = Node { 422 433 build_remotely: false, 423 434 ..Default::default() 424 435 }; 425 436 let name = &Name(function_name!().into()); 426 - let executor = GoalExecutor::new(Context::create_test_context(path, name, &mut node)); 437 + let executor = GoalExecutor::new(Context::create_test_context(location, name, &mut node)); 427 438 let steps = get_steps(executor); 428 439 429 440 assert_eq!( ··· 449 460 450 461 #[tokio::test] 451 462 async fn order_keys_only() { 452 - let path = get_test_path!(); 463 + let location = location!(get_test_path!()); 453 464 let mut node = Node::default(); 454 465 let name = &Name(function_name!().into()); 455 - let mut context = Context::create_test_context(path, name, &mut node); 466 + let mut context = Context::create_test_context(location, name, &mut node); 456 467 457 468 context.goal = Goal::Keys; 458 469 ··· 474 485 475 486 #[tokio::test] 476 487 async fn order_build_only() { 477 - let path = get_test_path!(); 488 + let location = location!(get_test_path!()); 478 489 let mut node = Node::default(); 479 490 let name = &Name(function_name!().into()); 480 - let mut context = Context::create_test_context(path, name, &mut node); 491 + let mut context = Context::create_test_context(location, name, &mut node); 481 492 482 493 context.goal = Goal::Build; 483 494 ··· 497 508 498 509 #[tokio::test] 499 510 async fn order_push_only() { 500 - let path = get_test_path!(); 511 + let location = location!(get_test_path!()); 501 512 let mut node = Node::default(); 502 513 let name = &Name(function_name!().into()); 503 - let mut context = Context::create_test_context(path, name, &mut node); 514 + let mut context = Context::create_test_context(location, name, &mut node); 504 515 505 516 context.goal = Goal::Push; 506 517 ··· 519 530 520 531 #[tokio::test] 521 532 async fn order_remote_build() { 522 - let path = get_test_path!(); 533 + let location = location!(get_test_path!()); 523 534 let mut node = Node { 524 535 build_remotely: true, 525 536 ..Default::default() 526 537 }; 527 538 528 539 let name = &Name(function_name!().into()); 529 - let executor = GoalExecutor::new(Context::create_test_context(path, name, &mut node)); 540 + let executor = GoalExecutor::new(Context::create_test_context(location, name, &mut node)); 530 541 let steps = get_steps(executor); 531 542 532 543 assert_eq!(
+1 -1
wire/lib/src/hive/steps/evaluate.rs
··· 28 28 #[instrument(skip_all, name = "eval")] 29 29 async fn execute(&self, ctx: &mut Context<'_>) -> Result<(), HiveLibError> { 30 30 let output = evaluate_hive_attribute( 31 - &ctx.hivepath, 31 + &ctx.hive_location, 32 32 &EvalGoal::GetTopLevel(ctx.name), 33 33 ctx.modifiers, 34 34 ctx.clobber_lock.clone(),
+6
wire/lib/src/test_macros.rs
··· 25 25 .last() 26 26 .unwrap(); 27 27 path.push(function_name); 28 + 28 29 path 29 30 }}; 30 31 } 32 + 33 + #[macro_export] 34 + macro_rules! location { 35 + ($path:expr) => {{ $crate::hive::get_hive_location($path.display().to_string()).unwrap() }}; 36 + }