difftreelog
build update to nixos release-25.05
in: trunk
34 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
-version = 3
+version = 4
[[package]]
name = "abort-on-drop"
@@ -377,29 +377,6 @@
]
[[package]]
-name = "bindgen"
-version = "0.69.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088"
-dependencies = [
- "bitflags",
- "cexpr",
- "clang-sys",
- "itertools 0.12.1",
- "lazy_static",
- "lazycell",
- "log",
- "prettyplease",
- "proc-macro2",
- "quote",
- "regex",
- "rustc-hash",
- "shlex",
- "syn",
- "which",
-]
-
-[[package]]
name = "bitflags"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -488,15 +465,6 @@
]
[[package]]
-name = "cexpr"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
-dependencies = [
- "nom 7.1.3",
-]
-
-[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -556,17 +524,6 @@
"crypto-common",
"inout",
"zeroize",
-]
-
-[[package]]
-name = "clang-sys"
-version = "1.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
-dependencies = [
- "glob",
- "libc",
- "libloading",
]
[[package]]
@@ -1242,12 +1199,6 @@
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
-
-[[package]]
-name = "glob"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "h2"
@@ -1332,15 +1283,6 @@
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
"digest",
-]
-
-[[package]]
-name = "home"
-version = "0.5.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
-dependencies = [
- "windows-sys 0.52.0",
]
[[package]]
@@ -1648,15 +1590,6 @@
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
-
-[[package]]
-name = "itertools"
-version = "0.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
-dependencies = [
- "either",
-]
[[package]]
name = "itertools"
@@ -1701,28 +1634,12 @@
]
[[package]]
-name = "lazycell"
-version = "1.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
-
-[[package]]
name = "libc"
version = "0.2.174"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
[[package]]
-name = "libloading"
-version = "0.8.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
-dependencies = [
- "cfg-if",
- "windows-targets",
-]
-
-[[package]]
name = "libm"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1887,14 +1804,6 @@
"tokio-util",
"tracing",
"unindent",
-]
-
-[[package]]
-name = "nix-native-eval"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "nixrs",
]
[[package]]
@@ -1912,25 +1821,6 @@
]
[[package]]
-name = "nixrs"
-version = "0.1.0"
-source = "git+https://github.com/Anillc/nixrs#740fcf4048cc5b6de8c54d18254f12d53909a867"
-dependencies = [
- "libc",
- "nixrs-sys",
- "thiserror 1.0.69",
-]
-
-[[package]]
-name = "nixrs-sys"
-version = "0.1.0"
-source = "git+https://github.com/Anillc/nixrs#740fcf4048cc5b6de8c54d18254f12d53909a867"
-dependencies = [
- "bindgen",
- "pkg-config",
-]
-
-[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2224,12 +2114,6 @@
"der",
"spki",
]
-
-[[package]]
-name = "pkg-config"
-version = "0.3.31"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
[[package]]
name = "poly1305"
@@ -3752,18 +3636,6 @@
dependencies = [
"js-sys",
"wasm-bindgen",
-]
-
-[[package]]
-name = "which"
-version = "4.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
-dependencies = [
- "either",
- "home",
- "once_cell",
- "rustix 0.38.40",
]
[[package]]
Cargo.tomldiffbeforeafterboth--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,8 +2,8 @@
members = ["crates/*", "cmds/*"]
resolver = "2"
package.version = "0.1.0"
-package.edition = "2021"
-package.rust-version = "1.82.0"
+package.edition = "2024"
+package.rust-version = "1.86.0"
[workspace.dependencies]
better-command = { path = "./crates/better-command" }
cmds/fleet/src/cmds/build_systems.rsdiffbeforeafterboth--- a/cmds/fleet/src/cmds/build_systems.rs
+++ b/cmds/fleet/src/cmds/build_systems.rs
@@ -1,15 +1,15 @@
use std::{env::current_dir, os::unix::fs::symlink, path::PathBuf};
-use anyhow::{anyhow, Result};
+use anyhow::{Result, anyhow};
use clap::Parser;
use fleet_base::{
- deploy::{deploy_task, upload_task, DeployAction},
+ deploy::{DeployAction, deploy_task, upload_task},
host::{Config, DeployKind, GenerationStorage},
opts::FleetOpts,
};
-use nix_eval::{nix_go, NixBuildBatch};
+use nix_eval::{NixBuildBatch, nix_go};
use tokio::task::LocalSet;
-use tracing::{error, field, info, info_span, warn, Instrument};
+use tracing::{Instrument, error, field, info, info_span, warn};
#[derive(Parser)]
pub struct Deploy {
@@ -167,11 +167,12 @@
self.action,
&host,
remote_path,
- if let Ok(v) = opts.action_attr(&host, "specialisation").await {
- v
- } else {
- error!("unreachable? failed to get specialization");
- return;
+ match opts.action_attr(&host, "specialisation").await {
+ Ok(v) => v,
+ _ => {
+ error!("unreachable? failed to get specialization");
+ return;
+ }
},
disable_rollback,
)
cmds/fleet/src/cmds/info.rsdiffbeforeafterboth--- a/cmds/fleet/src/cmds/info.rs
+++ b/cmds/fleet/src/cmds/info.rs
@@ -1,6 +1,6 @@
use std::collections::BTreeSet;
-use anyhow::{ensure, Result};
+use anyhow::{Result, ensure};
use clap::Parser;
use fleet_base::host::Config;
use nix_eval::nix_go_json;
cmds/fleet/src/cmds/mod.rsdiffbeforeafterboth--- a/cmds/fleet/src/cmds/mod.rs
+++ b/cmds/fleet/src/cmds/mod.rs
@@ -1,6 +1,6 @@
pub mod build_systems;
pub mod complete;
pub mod info;
+pub mod rollback;
pub mod secrets;
pub mod tf;
-pub mod rollback;
\ No newline at end of file
cmds/fleet/src/cmds/rollback.rsdiffbeforeafterboth--- a/cmds/fleet/src/cmds/rollback.rs
+++ b/cmds/fleet/src/cmds/rollback.rs
@@ -1,9 +1,9 @@
use std::collections::HashSet;
-use anyhow::{bail, Result};
+use anyhow::{Result, bail};
use clap::Parser;
use fleet_base::{
- deploy::{deploy_task, upload_task, DeployAction},
+ deploy::{DeployAction, deploy_task, upload_task},
host::{Config, ConfigHost, Generation, GenerationStorage},
opts::FleetOpts,
};
cmds/fleet/src/cmds/secrets/mod.rsdiffbeforeafterboth--- a/cmds/fleet/src/cmds/secrets/mod.rs
+++ b/cmds/fleet/src/cmds/secrets/mod.rs
@@ -1,25 +1,25 @@
use std::{
collections::{BTreeMap, BTreeSet, HashSet},
- io::{self, stdin, stdout, Read, Write},
+ io::{self, Read, Write, stdin, stdout},
path::PathBuf,
};
use age::Recipient;
-use anyhow::{anyhow, bail, ensure, Context, Result};
+use anyhow::{Context, Result, anyhow, bail, ensure};
use chrono::{DateTime, Utc};
use clap::Parser;
use fleet_base::{
- fleetdata::{encrypt_secret_data, FleetSecret, FleetSecretPart, FleetSharedSecret},
+ fleetdata::{FleetSecret, FleetSecretPart, FleetSharedSecret, encrypt_secret_data},
host::Config,
opts::FleetOpts,
};
use fleet_shared::SecretData;
-use nix_eval::{nix_go, nix_go_json, NixBuildBatch, Value};
+use nix_eval::{NixBuildBatch, Value, nix_go, nix_go_json};
use owo_colors::OwoColorize;
use serde::Deserialize;
use tabled::{Table, Tabled};
use tokio::fs::read;
-use tracing::{error, info, info_span, warn, Instrument};
+use tracing::{Instrument, error, info, info_span, warn};
#[derive(Parser)]
pub enum Secret {
@@ -187,7 +187,9 @@
true
} else if set.difference(&expected_set).next().is_some() {
// TODO: Remove this warning for revokable secrets.
- warn!("host was removed from secret owners, but until this host rebuild, the secret will still be stored on it.");
+ warn!(
+ "host was removed from secret owners, but until this host rebuild, the secret will still be stored on it."
+ );
nix_go_json!(field.regenerateOnOwnerRemoved)
} else if expected_set.difference(&set).next().is_some() {
nix_go_json!(field.regenerateOnOwnerAdded)
@@ -296,8 +298,8 @@
let out_parent = host.mktemp_dir().await?;
let out = format!("{out_parent}/out");
- let mut gen = host.cmd(generator).await?;
- gen.env("out", &out);
+ let mut r#gen = host.cmd(generator).await?;
+ r#gen.env("out", &out);
if on.is_none() {
// This path is local, thus we can feed `OsString` directly to env var... But I don't think that's necessary to handle.
let project_path: String = config
@@ -306,9 +308,9 @@
.into_os_string()
.into_string()
.map_err(|s| anyhow!("fleet project path is not utf-8: {s:?}"))?;
- gen.env("FLEET_PROJECT", project_path);
+ r#gen.env("FLEET_PROJECT", project_path);
}
- gen.run().await.context("impure generator")?;
+ r#gen.run().await.context("impure generator")?;
{
let marker = host.read_file_text(format!("{out}/marker")).await?;
@@ -510,7 +512,9 @@
if !remove_machines.is_empty() {
// TODO: maybe force secret regeneration?
// Not that useful without revokation.
- warn!("secret will not be regenerated for removed machines, and until host rebuild, they will still possess the ability to decode secret");
+ warn!(
+ "secret will not be regenerated for removed machines, and until host rebuild, they will still possess the ability to decode secret"
+ );
}
Ok(target_machines)
}
@@ -596,7 +600,9 @@
part: part_name,
} => {
if config.has_secret(&machine, &name) && !replace && !merge {
- bail!("secret already defined.\nUse --replace to override, or --merge to add new parts to existing secret");
+ bail!(
+ "secret already defined.\nUse --replace to override, or --merge to add new parts to existing secret"
+ );
}
let mut out = if merge && !replace {
cmds/fleet/src/extra_args.rsdiffbeforeafterboth--- a/cmds/fleet/src/extra_args.rs
+++ b/cmds/fleet/src/extra_args.rs
@@ -1,6 +1,6 @@
use std::ffi::{OsStr, OsString};
-use anyhow::{anyhow, Result};
+use anyhow::{Result, anyhow};
pub fn parse_os(os: &OsStr) -> Result<Vec<OsString>> {
Ok(shlex::bytes::split(os.as_encoded_bytes())
cmds/fleet/src/main.rsdiffbeforeafterboth--- a/cmds/fleet/src/main.rs
+++ b/cmds/fleet/src/main.rs
@@ -6,27 +6,27 @@
use std::{ffi::OsString, process::ExitCode};
-use anyhow::{bail, Result};
+use anyhow::{Result, bail};
use clap::{CommandFactory, Parser};
use cmds::{
build_systems::{BuildSystems, Deploy},
- rollback::RollbackSingle,
complete::Complete,
info::Info,
+ rollback::RollbackSingle,
secrets::Secret,
tf::Tf,
};
use fleet_base::{host::Config, opts::FleetOpts};
-use futures::{future::LocalBoxFuture, stream::FuturesUnordered, TryStreamExt};
+use futures::{TryStreamExt, future::LocalBoxFuture, stream::FuturesUnordered};
// use host::Config;
#[cfg(feature = "indicatif")]
use human_repr::HumanCount;
#[cfg(feature = "indicatif")]
use indicatif::{ProgressState, ProgressStyle};
-use tracing::{error, info, info_span, Instrument};
+use tracing::{Instrument, error, info, info_span};
#[cfg(feature = "indicatif")]
use tracing_indicatif::IndicatifLayer;
-use tracing_subscriber::{prelude::*, EnvFilter};
+use tracing_subscriber::{EnvFilter, prelude::*};
#[derive(Parser)]
struct Prefetch {}
cmds/generator-helper/src/main.rsdiffbeforeafterboth--- a/cmds/generator-helper/src/main.rs
+++ b/cmds/generator-helper/src/main.rs
@@ -1,21 +1,22 @@
use std::{
env,
fs::{File, OpenOptions},
- io::{self, copy, stdin, stdout, Read, Write},
+ io::{self, Read, Write, copy, stdin, stdout},
str::FromStr,
};
use age::{
+ Encryptor, Recipient,
ssh::{ParseRecipientKeyError, Recipient as SshRecipient},
- Encryptor, Recipient,
};
-use anyhow::{anyhow, bail, ensure, Context, Result};
+use anyhow::{Context, Result, anyhow, bail, ensure};
use clap::{Parser, ValueEnum};
use ed25519_dalek::SecretKey;
use fleet_shared::SecretData;
use rand::{
+ RngCore,
distr::{Alphanumeric, Distribution, SampleString, Uniform},
- rng, RngCore,
+ rng,
};
fn write_output_file(out: &str) -> Result<File> {
@@ -78,7 +79,9 @@
let list = match list {
Ok(v) => v,
Err(env::VarError::NotPresent) => {
- bail!("gh is only intended to be used from secret generator scripts, but if you really want to use it somewhere else - set GENERATOR_HELPER_IDENTITIES to list of newline-delimited ssh identities");
+ bail!(
+ "gh is only intended to be used from secret generator scripts, but if you really want to use it somewhere else - set GENERATOR_HELPER_IDENTITIES to list of newline-delimited ssh identities"
+ );
}
Err(e) => bail!("somehow, identities list is not utf-8: {e}"),
};
@@ -254,13 +257,7 @@
write_private(
&recipients,
&private,
- &key[..{
- if no_embed_public {
- 32
- } else {
- 64
- }
- }],
+ &key[..{ if no_embed_public { 32 } else { 64 } }],
encoding,
)?;
}
cmds/install-secrets/src/main.rsdiffbeforeafterboth--- a/cmds/install-secrets/src/main.rs
+++ b/cmds/install-secrets/src/main.rs
@@ -5,20 +5,20 @@
iter,
os::unix::prelude::PermissionsExt,
path::{Path, PathBuf},
- str::{from_utf8, FromStr},
+ str::{FromStr, from_utf8},
};
use age::{
- ssh::{Identity as SshIdentity, Recipient as SshRecipient},
Decryptor, Encryptor, Identity, Recipient,
+ ssh::{Identity as SshIdentity, Recipient as SshRecipient},
};
-use anyhow::{anyhow, bail, ensure, Context, Result};
+use anyhow::{Context, Result, anyhow, bail, ensure};
use clap::Parser;
use fleet_shared::SecretData;
-use nix::unistd::{chown, Group, User};
+use nix::unistd::{Group, User, chown};
use serde::Deserialize;
use tracing::{error, info, info_span};
-use tracing_subscriber::{filter::LevelFilter, EnvFilter};
+use tracing_subscriber::{EnvFilter, filter::LevelFilter};
#[derive(Parser)]
#[clap(author)]
cmds/terraform-provider-fleet/src/main.rsdiffbeforeafterboth--- a/cmds/terraform-provider-fleet/src/main.rs
+++ b/cmds/terraform-provider-fleet/src/main.rs
@@ -1,3 +1,5 @@
fn main() {
- panic!("this is a stub, real provider is in development, I just don't want to keep it in different branch.")
+ panic!(
+ "this is a stub, real provider is in development, I just don't want to keep it in different branch."
+ )
}
crates/better-command/src/handler.rsdiffbeforeafterboth--- a/crates/better-command/src/handler.rs
+++ b/crates/better-command/src/handler.rs
@@ -7,7 +7,7 @@
use regex::Regex;
use serde::Deserialize;
-use tracing::{info, info_span, warn, Span};
+use tracing::{Span, info, info_span, warn};
#[cfg(feature = "indicatif")]
use tracing_indicatif::span_ext::IndicatifSpanExt as _;
@@ -112,9 +112,13 @@
match log {
NixLog::Msg { msg, raw_msg, .. } => {
#[allow(clippy::nonminimal_bool)]
- if !(msg.starts_with("\u{1b}[35;1mwarning:\u{1b}[0m Git tree '") && msg.ends_with("' is dirty"))
- && !msg.starts_with("\u{1b}[35;1mwarning:\u{1b}[0m not writing modified lock file of flake")
- && msg != "\u{1b}[35;1mwarning:\u{1b}[0m \u{1b}[31;1merror:\u{1b}[0m SQLite database '\u{1b}[35;1m/nix/var/nix/db/db.sqlite\u{1b}[0m' is busy" {
+ if !(msg.starts_with("\u{1b}[35;1mwarning:\u{1b}[0m Git tree '")
+ && msg.ends_with("' is dirty"))
+ && !msg.starts_with(
+ "\u{1b}[35;1mwarning:\u{1b}[0m not writing modified lock file of flake",
+ ) && msg
+ != "\u{1b}[35;1mwarning:\u{1b}[0m \u{1b}[31;1merror:\u{1b}[0m SQLite database '\u{1b}[35;1m/nix/var/nix/db/db.sqlite\u{1b}[0m' is busy"
+ {
if let Some(raw_msg) = raw_msg {
if !msg.is_empty() {
info!(target: "nix", "{}\n{}", raw_msg.trim_end(), msg.trim_end())
@@ -156,8 +160,12 @@
id,
..
} if typ == 100 && fields.len() >= 3 => {
- if let [LogField::String(drv), LogField::String(from), LogField::String(to), ..] =
- &fields[..]
+ if let [
+ LogField::String(drv),
+ LogField::String(from),
+ LogField::String(to),
+ ..,
+ ] = &fields[..]
{
let mut drv = drv.as_str();
@@ -289,8 +297,12 @@
}
NixLog::Result { fields, id, typ } if typ == 105 && fields.len() >= 4 => {
if let Some(span) = self.spans.get(&id) {
- if let [LogField::Num(done), LogField::Num(expected), LogField::Num(_running), LogField::Num(_failed)] =
- &fields[..4]
+ if let [
+ LogField::Num(done),
+ LogField::Num(expected),
+ LogField::Num(_running),
+ LogField::Num(_failed),
+ ] = &fields[..4]
{
#[cfg(feature = "indicatif")]
{
crates/fleet-base/src/command.rsdiffbeforeafterboth--- a/crates/fleet-base/src/command.rs
+++ b/crates/fleet-base/src/command.rs
@@ -1,6 +1,6 @@
use std::{ffi::OsStr, pin, process::Stdio, sync::Arc, task::Poll};
-use anyhow::{anyhow, Result};
+use anyhow::{Result, anyhow};
use better_command::{Handler, NixHandler, PlainHandler};
use futures::StreamExt;
use itertools::Either;
@@ -68,10 +68,9 @@
}
}
fn new_here(&self, cmd: impl AsRef<OsStr>) -> Self {
- if let Some(ssh_session) = self.ssh_session.clone() {
- Self::new_on(self.escalation, cmd, ssh_session)
- } else {
- Self::new(self.escalation, cmd)
+ match self.ssh_session.clone() {
+ Some(ssh_session) => Self::new_on(self.escalation, cmd, ssh_session),
+ _ => Self::new(self.escalation, cmd),
}
}
@@ -139,15 +138,18 @@
out
}
fn into_command(self) -> Result<Either<Command, openssh::OwningCommand<Arc<Session>>>> {
- Ok(if let Some(session) = self.ssh_session.clone() {
- let cmd = self.translate_env_into_env().into_command_unchecked_local();
- Either::Right(
- cmd.over_ssh(session)
- .map_err(|e| anyhow!("ssh error: {e}"))?,
- )
- } else {
- let cmd = self.into_command_unchecked_local();
- Either::Left(cmd)
+ Ok(match self.ssh_session.clone() {
+ Some(session) => {
+ let cmd = self.translate_env_into_env().into_command_unchecked_local();
+ Either::Right(
+ cmd.over_ssh(session)
+ .map_err(|e| anyhow!("ssh error: {e}"))?,
+ )
+ }
+ _ => {
+ let cmd = self.into_command_unchecked_local();
+ Either::Left(cmd)
+ }
})
}
pub fn arg(&mut self, arg: impl AsRef<OsStr>) -> &mut Self {
crates/fleet-base/src/deploy.rsdiffbeforeafterboth--- a/crates/fleet-base/src/deploy.rs
+++ b/crates/fleet-base/src/deploy.rs
@@ -1,10 +1,10 @@
use std::{path::PathBuf, time::Duration};
-use anyhow::{anyhow, bail, Context as _, Result};
+use anyhow::{Context as _, Result, anyhow, bail};
use clap::ValueEnum;
use itertools::Itertools;
use tokio::time::sleep;
-use tracing::{error, info, info_span, warn, Instrument as _};
+use tracing::{Instrument as _, error, info, info_span, warn};
use crate::host::{Config, ConfigHost, DeployKind, Generation, GenerationStorage};
@@ -221,7 +221,9 @@
.in_current_span()
.await
{
- error!("failed to remove rollback marker. This is bad, as the system will be rolled back by watchdog: {e}")
+ error!(
+ "failed to remove rollback marker. This is bad, as the system will be rolled back by watchdog: {e}"
+ )
}
}
info!("disarming watchdog, just in case");
@@ -233,12 +235,17 @@
error!("failed to disarm rollback run: {e}");
}
}
- } else if let Err(_e) = host
- .rm_file("/etc/fleet_rollback_marker", true)
- .in_current_span()
- .await
- {
- // Marker might not exist, yet better try to remove it.
+ } else {
+ match host
+ .rm_file("/etc/fleet_rollback_marker", true)
+ .in_current_span()
+ .await
+ {
+ Err(_e) => {
+ // Marker might not exist, yet better try to remove it.
+ }
+ _ => {}
+ }
}
}
Ok(())
crates/fleet-base/src/fleetdata.rsdiffbeforeafterboth--- a/crates/fleet-base/src/fleetdata.rs
+++ b/crates/fleet-base/src/fleetdata.rs
@@ -10,7 +10,7 @@
distr::{Alphanumeric, SampleString as _},
rng,
};
-use serde::{de::Error, Deserialize, Serialize};
+use serde::{Deserialize, Serialize, de::Error};
use serde_json::Value;
#[derive(Serialize, Deserialize, Default)]
crates/fleet-base/src/host.rsdiffbeforeafterboth--- a/crates/fleet-base/src/host.rs
+++ b/crates/fleet-base/src/host.rs
@@ -10,14 +10,14 @@
sync::{Arc, Mutex, MutexGuard, OnceLock},
};
-use anyhow::{anyhow, bail, ensure, Context, Result};
+use anyhow::{Context, Result, anyhow, bail, ensure};
use fleet_shared::SecretData;
-use nix_eval::{nix_go, nix_go_json, util::assert_warn, NixSession, Value};
+use nix_eval::{NixSession, Value, nix_go, nix_go_json, util::assert_warn};
use openssh::SessionBuilder;
use serde::de::DeserializeOwned;
use tabled::Tabled;
use tempfile::NamedTempFile;
-use time::{format_description, UtcDateTime};
+use time::{UtcDateTime, format_description};
use tracing::warn;
use crate::{
@@ -87,7 +87,9 @@
"fleet" => Ok(Self::Fleet),
"nixos-install" => Ok(Self::NixosInstall),
"nixos-lustrate" => Ok(Self::NixosLustrate),
- v => bail!("unknown deploy_kind: {v}; expected on of \"upgrade-to-fleet\", \"fleet\", \"nixos-install\", \"nixos-lustrate\""),
+ v => bail!(
+ "unknown deploy_kind: {v}; expected on of \"upgrade-to-fleet\", \"fleet\", \"nixos-install\", \"nixos-lustrate\""
+ ),
}
}
}
@@ -189,11 +191,11 @@
.map(|e| e.trim())
.filter(|&l| !l.is_empty())
.filter_map(|g| {
- let gen = parse_generation_line(g);
- if gen.is_none() {
+ let generation = parse_generation_line(g);
+ if generation.is_none() {
warn!("bad generation: {g}");
};
- gen
+ generation
})
.collect::<Vec<_>>();
for ele in generations.iter_mut() {
crates/fleet-base/src/keys.rsdiffbeforeafterboth--- a/crates/fleet-base/src/keys.rs
+++ b/crates/fleet-base/src/keys.rs
@@ -1,7 +1,7 @@
use std::str::FromStr as _;
use age::Recipient;
-use anyhow::{anyhow, Result};
+use anyhow::{Result, anyhow};
use futures::{StreamExt as _, TryStreamExt as _};
use itertools::Itertools as _;
use tracing::warn;
@@ -39,12 +39,12 @@
}
}
/// Insecure, requires root
- pub async fn recipient(&self, host: &str) -> anyhow::Result<impl Recipient> {
+ pub async fn recipient(&self, host: &str) -> anyhow::Result<impl Recipient + use<>> {
let key = self.key(host).await?;
age::ssh::Recipient::from_str(&key).map_err(|e| anyhow!("parse recipient error: {:?}", e))
}
- pub async fn recipients(&self, hosts: Vec<String>) -> Result<Vec<impl Recipient>> {
+ pub async fn recipients(&self, hosts: Vec<String>) -> Result<Vec<impl Recipient + use<>>> {
let hosts = self.expand_owner_set(hosts).await?;
futures::stream::iter(hosts.iter())
.then(|m| self.recipient(m.as_ref()))
crates/fleet-base/src/lib.rsdiffbeforeafterboth--- a/crates/fleet-base/src/lib.rs
+++ b/crates/fleet-base/src/lib.rs
@@ -1,6 +1,6 @@
pub mod command;
+pub mod deploy;
pub mod fleetdata;
pub mod host;
mod keys;
pub mod opts;
-pub mod deploy;
\ No newline at end of file
crates/fleet-base/src/opts.rsdiffbeforeafterboth--- a/crates/fleet-base/src/opts.rs
+++ b/crates/fleet-base/src/opts.rs
@@ -6,15 +6,15 @@
sync::{Arc, Mutex},
};
-use anyhow::{bail, Context, Result};
-use nix_eval::{nix_go, util::assert_warn, NixSessionPool, Value};
+use anyhow::{Context, Result, bail};
+use nix_eval::{NixSessionPool, Value, nix_go, util::assert_warn};
use nom::{
+ Parser,
bytes::complete::take_while1,
character::complete::char,
combinator::{map, opt},
multi::separated_list1,
sequence::{preceded, separated_pair},
- Parser,
};
use crate::{
@@ -44,7 +44,8 @@
let (input, name) = map(
take_while1(|v| v != ',' && v != '?' && v != '@'),
str::to_owned,
- ).parse_complete(input)
+ )
+ .parse_complete(input)
.map_err(err_to_string)?;
let kw_item = separated_pair(
crates/fleet-shared/src/encoding.rsdiffbeforeafterboth--- a/crates/fleet-shared/src/encoding.rs
+++ b/crates/fleet-shared/src/encoding.rs
@@ -3,8 +3,8 @@
str::FromStr,
};
-use base64::engine::{general_purpose::STANDARD_NO_PAD, Engine};
-use serde::{de::Error, Deserialize, Deserializer, Serialize};
+use base64::engine::{Engine, general_purpose::STANDARD_NO_PAD};
+use serde::{Deserialize, Deserializer, Serialize, de::Error};
use unicode_categories::UnicodeCategories;
#[derive(Debug, PartialEq, Clone)]
crates/nix-eval/src/pool.rsdiffbeforeafterboth--- a/crates/nix-eval/src/pool.rs
+++ b/crates/nix-eval/src/pool.rs
@@ -5,7 +5,7 @@
use r2d2::Pool;
-use crate::{session::NixSessionInner, Error, NixSession, Result};
+use crate::{Error, NixSession, Result, session::NixSessionInner};
pub struct NixSessionPool(Pool<NixSessionPoolInner>);
impl NixSessionPool {
crates/nix-eval/src/session.rsdiffbeforeafterboth1use std::{ffi::OsStr, num::ParseIntError, process::Stdio, sync::Arc};23use better_command::{ClonableHandler, Handler, NixHandler, NoopHandler};4use futures::StreamExt;5use itertools::Itertools as _;6use serde::{de::DeserializeOwned, Deserialize};7use thiserror::Error;8use tokio::{9 io::AsyncWriteExt,10 process::{ChildStderr, ChildStdin, ChildStdout, Command},11 select,12 sync::{mpsc, oneshot, Mutex},13};14use tokio_util::codec::{FramedRead, LinesCodec};15use tracing::{debug, error, warn, Level};1617#[derive(Error, Debug, Clone)]18pub enum Error {19 #[error("failed to create nix repl session: {0}")]20 SessionInit(&'static str),21 #[error("unexpected end of output, nix crashed?")]22 MissingDelimiter,2324 #[error("expression did'nt produce any output")]25 ExpectedOutput,26 #[error("expression produced output, which is unexpected")]27 UnexpectedOutput,2829 #[error("unexpected expression output type")]30 InvalidType,3132 #[error("failed to build attr {attribute}:\n{error}")]33 BuildFailed { attribute: String, error: String },3435 #[error("output: {0}")]36 Json(Arc<serde_json::Error>),37 // int outputs are too specific, and should not be used,38 // thus error is ok to be not informative.39 #[error("int output: {0}")]40 Int(ParseIntError),41 #[error("pool: {0}")]42 Pool(Arc<r2d2::Error>),43 #[error("io: {0}")]44 Io(Arc<std::io::Error>),4546 // TODO: Should be done by wrapper/in different type.47 #[error("at {0}: {1}")]48 InContext(String, Box<Self>),4950 #[error("error: {0}")]51 NixError(String),52}53impl From<r2d2::Error> for Error {54 fn from(value: r2d2::Error) -> Self {55 Self::Pool(Arc::new(value))56 }57}58impl From<std::io::Error> for Error {59 fn from(value: std::io::Error) -> Self {60 Self::Io(Arc::new(value))61 }62}63impl From<serde_json::Error> for Error {64 fn from(value: serde_json::Error) -> Self {65 Self::Json(Arc::new(value))66 }67}68impl Error {69 pub(crate) fn context(self, context: String) -> Self {70 Self::InContext(context, Box::new(self))71 }72}73pub type Result<T, E = Error> = std::result::Result<T, E>;7475enum OutputLine {76 Out(String),77 Err(String),78}79struct OutputHandler {80 rx: mpsc::Receiver<OutputLine>,81 _cancel_handle: oneshot::Receiver<()>,82}83impl OutputHandler {84 fn new(out: ChildStdout, err: ChildStderr) -> Self {85 let mut out = FramedRead::new(out, LinesCodec::new());86 let mut err = FramedRead::new(err, LinesCodec::new());87 let (tx, rx) = mpsc::channel(20);88 let (mut cancelled, _cancel_handle) = oneshot::channel();89 tokio::spawn(async move {90 loop {91 select! {92 // We should receive errors earlier than synchronization93 biased;94 e = err.next() => {95 let Some(Ok(e)) = e else {96 if e.is_some() {97 error!("bad repl stderr: {e:?}");98 }99 continue;100 };101 let _ = tx.send(OutputLine::Err(e)).await;102 }103 o = out.next() => {104 let Some(Ok(o)) = o else {105 if o.is_some() {106 error!("bad repl stdout: {o:?}");107 }108 continue;109 };110 let _ = tx.send(OutputLine::Out(o)).await;111 }112 // Reader doesn't care about stdout, as this is cancelled.113 // Error still might be useful, to process leftover span closures?114 _ = cancelled.closed() => {115 break;116 }117 }118 }119 });120 Self { rx, _cancel_handle }121 }122 async fn next(&mut self) -> Option<OutputLine> {123 self.rx.recv().await124 }125}126127#[must_use]128struct ErrorCollector<'i, H> {129 collected: Vec<String>,130 inner: &'i mut H,131}132impl<'i, H> ErrorCollector<'i, H> {133 fn new(inner: &'i mut H) -> Self {134 Self {135 collected: vec![],136 inner,137 }138 }139}140impl<H> ErrorCollector<'_, H> {141 fn handle_line_inner(&mut self, msg: &str) -> bool {142 let Some(msg) = msg.strip_prefix("@nix ") else {143 return false;144 };145 #[derive(Deserialize)]146 struct ErrorAction {147 action: String,148 level: u32,149 msg: String,150 }151 let Ok(act) = serde_json::from_str::<ErrorAction>(msg) else {152 return false;153 };154 if act.action != "msg" || act.level != 0 {155 return false;156 }157 self.collected.push(act.msg);158 true159 }160 fn finish(self) -> Result<()> {161 // fn dedent(s: String) -> String {162 // s.split('\n').filter(|s| !s.trim().is_empty()).map(|v| v.)163 // }164 if !self.collected.is_empty() {165 return Err(Error::NixError(166 self.collected167 .iter()168 .map(|v| {169 if let Some(f) = v.strip_prefix("\u{1b}[31;1merror:\u{1b}[0m ") {170 let v = unindent::unindent(f.trim_start());171 v.trim().to_owned()172 } else {173 v.to_owned()174 }175 })176 .join("\n")177 .to_string(),178 ));179 }180 Ok(())181 }182 fn flush(self) {183 for line in self.collected {184 warn!("{line}");185 }186 }187}188impl<H: Handler> Handler for ErrorCollector<'_, H> {189 fn handle_line(&mut self, e: &str) {190 if self.handle_line_inner(e) {191 return;192 }193 self.inner.handle_line(e)194 }195}196197pub struct NixSessionInner {198 full_delimiter: String,199 nix_handler: ClonableHandler<NixHandler>,200 out: OutputHandler,201 stdin: ChildStdin,202 string_wrapping: (String, String),203 number_wrapping: (String, String),204205 executing_command: Arc<Mutex<()>>,206207 next_id: u32,208 pub(crate) free_list: Vec<u32>,209210 pub nix_system: String,211}212213/// Discover inter-message repl delimiter214const REPL_DELIMITER: &str = "\"FLEET_MAGIC_REPL_DELIMITER\"";215/// Discover formatting around strings216const TRAIN_STRING: &str = "\"TRAIN_STRING\"";217/// Discover formatting around numbers218const TRAIN_NUMBER: &str = "13141516";219// Other types of formatting are not discovered, because they are not used, JSON serialization is used instead220// Techically, number training is also not required, because numbers can be converted to string too...221// Eh, I'll remove it later.222223impl NixSessionInner {224 pub(crate) async fn new(225 flake: &OsStr,226 extra_args: impl IntoIterator<Item = &OsStr>,227 nix_system: String,228 fail_fast: bool,229 ) -> Result<Self> {230 let mut cmd = Command::new("nix");231 cmd.arg("repl")232 .args(["--option", "pure-eval", "true"])233 .arg(flake)234 .arg("--log-format")235 .arg("internal-json");236 if !fail_fast {237 cmd.arg("--keep-going");238 }239 for arg in extra_args {240 cmd.arg(arg);241 }242 cmd.stdin(Stdio::piped());243 cmd.stdout(Stdio::piped());244 cmd.stderr(Stdio::piped());245 let cmd = cmd.spawn()?;246 let stdout = cmd.stdout.unwrap();247 let stderr = cmd.stderr.unwrap();248 let mut out = OutputHandler::new(stdout, stderr);249 let mut stdin = cmd.stdin.unwrap();250 // Standard repl hello doesn't work with internal-json logger251 stdin.write_all(REPL_DELIMITER.as_bytes()).await?;252 stdin.write_all(b"\n").await?;253 stdin.flush().await?;254 let nix_handler = NixHandler::default();255 let mut full_delimiter = None;256 let mut errors = vec![];257 while let Some(line) = out.next().await {258 let line = match line {259 OutputLine::Out(o) => o,260 OutputLine::Err(_e) => {261 // Handle startup errors, but skip repl hello?262 errors.push(_e);263 continue;264 }265 };266 if line.contains(REPL_DELIMITER) {267 debug!("discovered repl delimiter with added colors: {line}");268 full_delimiter = Some(line.to_owned());269 break;270 }271 }272 let Some(full_delimiter) = full_delimiter else {273 for e in errors {274 error!("{e}");275 }276 return Err(Error::SessionInit("failed to discover delimiter"));277 };278 let mut res = Self {279 full_delimiter,280 nix_handler: ClonableHandler::new(nix_handler),281 out,282 stdin,283 string_wrapping: Default::default(),284 number_wrapping: Default::default(),285286 executing_command: Arc::new(Mutex::new(())),287288 next_id: 0,289 free_list: vec![],290291 nix_system,292 };293 res.train().await?;294 Ok(res)295 }296 async fn train(&mut self) -> Result<()> {297 {298 let full_string = self299 .execute_expression_raw(TRAIN_STRING, &mut NoopHandler)300 .await?;301 let string_offset = full_string.find(TRAIN_STRING).expect("contained");302 let string_prefix = &full_string[..string_offset];303 let string_suffix = &full_string[string_offset + TRAIN_STRING.len()..];304 self.string_wrapping = (string_prefix.to_owned(), string_suffix.to_owned());305 }306 {307 let full_number = self308 .execute_expression_raw(TRAIN_NUMBER, &mut NoopHandler)309 .await?;310 let number_offset = full_number.find(TRAIN_NUMBER).expect("contained");311 let number_prefix = &full_number[..number_offset];312 let number_suffix = &full_number[number_offset + TRAIN_NUMBER.len()..];313 self.number_wrapping = (number_prefix.to_owned(), number_suffix.to_owned());314 }315 Ok(())316 }317 async fn send_command(&mut self, cmd: impl AsRef<[u8]>) -> Result<()> {318 if tracing::enabled!(Level::DEBUG) && cmd.as_ref() != REPL_DELIMITER.as_bytes() {319 let cmd_str = String::from_utf8_lossy(cmd.as_ref());320 tracing::debug!("{cmd_str}");321 };322 self.stdin.write_all(cmd.as_ref()).await?;323 self.stdin.write_all(b"\n").await?;324 Ok(())325 }326 async fn read_until_delimiter(&mut self, err_handler: &mut dyn Handler) -> Result<String> {327 let mut out = String::new();328 while let Some(line) = self.out.next().await {329 let line = match line {330 OutputLine::Out(out) => out,331 OutputLine::Err(err) => {332 err_handler.handle_line(&err);333 continue;334 }335 };336 if line == self.full_delimiter {337 return Ok(out);338 }339 if !out.is_empty() {340 out.push('\n');341 }342 out.push_str(&line);343 }344 Err(Error::MissingDelimiter)345 }346 pub(crate) async fn execute_expression_number(347 &mut self,348 expr: impl AsRef<[u8]>,349 ) -> Result<u64> {350 let num = self.number_wrapping.clone();351 let n = self.execute_expression_wrapping(expr, &num).await?;352 n.parse::<u64>().map_err(Error::Int)353 }354 async fn execute_expression_string(&mut self, expr: impl AsRef<[u8]>) -> Result<String> {355 // builtins.toJSON escapes some thing in incorrect way, e.g escaped "$" in "\${" is being outputed as "\$",356 // while this escape should be removed as it is intended for nix itself, not for json output.357 //358 // This regex only allows \$ in the beginning of the string, it is easier to implement correctly.359 // TODO: Add peg parser for nix-produced JSON?..360 let regex = regex::Regex::new(r#"(?<prefix>[: {,\[]\\")\\\$"#).expect("fixup json");361362 let num = self.string_wrapping.clone();363 let n = self.execute_expression_wrapping(expr, &num).await?;364 let n = regex.replace_all(&n, "$prefix$$");365 let str: String = serde_json::from_str(&n)?;366 Ok(str)367 }368 pub(crate) async fn execute_expression_to_json<V: DeserializeOwned>(369 &mut self,370 expr: impl AsRef<[u8]>,371 ) -> Result<V> {372 let mut fexpr = b"builtins.toJSON (".to_vec();373 fexpr.extend_from_slice(expr.as_ref());374 fexpr.push(b')');375376 Ok(serde_json::from_str(377 &self.execute_expression_string(fexpr).await?,378 )?)379 }380 async fn execute_expression_wrapping(381 &mut self,382 expr: impl AsRef<[u8]>,383 wrapping: &(String, String),384 ) -> Result<String> {385 let mut nix_handler = self.nix_handler.clone();386 let mut collected = ErrorCollector::new(&mut nix_handler);387 let res = self.execute_expression_raw(expr, &mut collected).await?;388 if res.is_empty() {389 collected.finish()?;390 return Err(Error::ExpectedOutput);391 } else {392 collected.flush()393 };394 let Some(res) = res.strip_prefix(&wrapping.0) else {395 return Err(Error::InvalidType);396 };397 let Some(res) = res.strip_suffix(&wrapping.1) else {398 return Err(Error::InvalidType);399 };400 Ok(res.to_owned())401 }402 async fn execute_expression_empty(&mut self, expr: impl AsRef<[u8]>) -> Result<()> {403 let mut nix_handler = self.nix_handler.clone();404 let mut collected = ErrorCollector::new(&mut nix_handler);405 let v = self.execute_expression_raw(expr, &mut collected).await?;406 collected.finish()?;407 if !v.is_empty() {408 return Err(Error::UnexpectedOutput);409 }410 Ok(())411 }412 pub(crate) async fn execute_expression_raw(413 &mut self,414 expr: impl AsRef<[u8]>,415 err_handler: &mut dyn Handler,416 ) -> Result<String> {417 // Prevent two commands from being executed in parallel, messing with each other.418 let _lock = self.executing_command.clone();419 let _guard = _lock.lock().await;420421 self.send_command(expr).await?;422 // It will be echoed423 self.send_command(REPL_DELIMITER).await?;424 self.read_until_delimiter(err_handler).await425 }426 pub(crate) async fn execute_assign(&mut self, expr: impl AsRef<str>) -> Result<u32> {427 let id = self.allocate_id();428 self.execute_expression_empty(format!("sess_field_{id} = {}", expr.as_ref()))429 .await?;430 Ok(id)431 }432433 /// Id should be immediately used434 fn allocate_id(&mut self) -> u32 {435 if let Some(free) = self.free_list.pop() {436 free437 } else {438 let v = self.next_id;439 self.next_id += 1;440 v441 }442 }443 // Nix has no way to deallocate variable, yet GC will correct everything not reachable.444 // async fn free_id(&mut self, id: u32) -> Result<()> {445 // self.execute_expression_empty(format!("sess_field_{id} = null"))446 // .await?;447 // self.free_list.push(id);448 // Ok(())449 // }450}1use std::{ffi::OsStr, num::ParseIntError, process::Stdio, sync::Arc};23use better_command::{ClonableHandler, Handler, NixHandler, NoopHandler};4use futures::StreamExt;5use itertools::Itertools as _;6use serde::{Deserialize, de::DeserializeOwned};7use thiserror::Error;8use tokio::{9 io::AsyncWriteExt,10 process::{ChildStderr, ChildStdin, ChildStdout, Command},11 select,12 sync::{Mutex, mpsc, oneshot},13};14use tokio_util::codec::{FramedRead, LinesCodec};15use tracing::{Level, debug, error, warn};1617#[derive(Error, Debug, Clone)]18pub enum Error {19 #[error("failed to create nix repl session: {0}")]20 SessionInit(&'static str),21 #[error("unexpected end of output, nix crashed?")]22 MissingDelimiter,2324 #[error("expression did'nt produce any output")]25 ExpectedOutput,26 #[error("expression produced output, which is unexpected")]27 UnexpectedOutput,2829 #[error("unexpected expression output type")]30 InvalidType,3132 #[error("failed to build attr {attribute}:\n{error}")]33 BuildFailed { attribute: String, error: String },3435 #[error("output: {0}")]36 Json(Arc<serde_json::Error>),37 // int outputs are too specific, and should not be used,38 // thus error is ok to be not informative.39 #[error("int output: {0}")]40 Int(ParseIntError),41 #[error("pool: {0}")]42 Pool(Arc<r2d2::Error>),43 #[error("io: {0}")]44 Io(Arc<std::io::Error>),4546 // TODO: Should be done by wrapper/in different type.47 #[error("at {0}: {1}")]48 InContext(String, Box<Self>),4950 #[error("error: {0}")]51 NixError(String),52}53impl From<r2d2::Error> for Error {54 fn from(value: r2d2::Error) -> Self {55 Self::Pool(Arc::new(value))56 }57}58impl From<std::io::Error> for Error {59 fn from(value: std::io::Error) -> Self {60 Self::Io(Arc::new(value))61 }62}63impl From<serde_json::Error> for Error {64 fn from(value: serde_json::Error) -> Self {65 Self::Json(Arc::new(value))66 }67}68impl Error {69 pub(crate) fn context(self, context: String) -> Self {70 Self::InContext(context, Box::new(self))71 }72}73pub type Result<T, E = Error> = std::result::Result<T, E>;7475enum OutputLine {76 Out(String),77 Err(String),78}79struct OutputHandler {80 rx: mpsc::Receiver<OutputLine>,81 _cancel_handle: oneshot::Receiver<()>,82}83impl OutputHandler {84 fn new(out: ChildStdout, err: ChildStderr) -> Self {85 let mut out = FramedRead::new(out, LinesCodec::new());86 let mut err = FramedRead::new(err, LinesCodec::new());87 let (tx, rx) = mpsc::channel(20);88 let (mut cancelled, _cancel_handle) = oneshot::channel();89 tokio::spawn(async move {90 loop {91 select! {92 // We should receive errors earlier than synchronization93 biased;94 e = err.next() => {95 let Some(Ok(e)) = e else {96 if e.is_some() {97 error!("bad repl stderr: {e:?}");98 }99 continue;100 };101 let _ = tx.send(OutputLine::Err(e)).await;102 }103 o = out.next() => {104 let Some(Ok(o)) = o else {105 if o.is_some() {106 error!("bad repl stdout: {o:?}");107 }108 continue;109 };110 let _ = tx.send(OutputLine::Out(o)).await;111 }112 // Reader doesn't care about stdout, as this is cancelled.113 // Error still might be useful, to process leftover span closures?114 _ = cancelled.closed() => {115 break;116 }117 }118 }119 });120 Self { rx, _cancel_handle }121 }122 async fn next(&mut self) -> Option<OutputLine> {123 self.rx.recv().await124 }125}126127#[must_use]128struct ErrorCollector<'i, H> {129 collected: Vec<String>,130 inner: &'i mut H,131}132impl<'i, H> ErrorCollector<'i, H> {133 fn new(inner: &'i mut H) -> Self {134 Self {135 collected: vec![],136 inner,137 }138 }139}140impl<H> ErrorCollector<'_, H> {141 fn handle_line_inner(&mut self, msg: &str) -> bool {142 let Some(msg) = msg.strip_prefix("@nix ") else {143 return false;144 };145 #[derive(Deserialize)]146 struct ErrorAction {147 action: String,148 level: u32,149 msg: String,150 }151 let Ok(act) = serde_json::from_str::<ErrorAction>(msg) else {152 return false;153 };154 if act.action != "msg" || act.level != 0 {155 return false;156 }157 self.collected.push(act.msg);158 true159 }160 fn finish(self) -> Result<()> {161 // fn dedent(s: String) -> String {162 // s.split('\n').filter(|s| !s.trim().is_empty()).map(|v| v.)163 // }164 if !self.collected.is_empty() {165 return Err(Error::NixError(166 self.collected167 .iter()168 .map(|v| {169 if let Some(f) = v.strip_prefix("\u{1b}[31;1merror:\u{1b}[0m ") {170 let v = unindent::unindent(f.trim_start());171 v.trim().to_owned()172 } else {173 v.to_owned()174 }175 })176 .join("\n")177 .to_string(),178 ));179 }180 Ok(())181 }182 fn flush(self) {183 for line in self.collected {184 warn!("{line}");185 }186 }187}188impl<H: Handler> Handler for ErrorCollector<'_, H> {189 fn handle_line(&mut self, e: &str) {190 if self.handle_line_inner(e) {191 return;192 }193 self.inner.handle_line(e)194 }195}196197pub struct NixSessionInner {198 full_delimiter: String,199 nix_handler: ClonableHandler<NixHandler>,200 out: OutputHandler,201 stdin: ChildStdin,202 string_wrapping: (String, String),203 number_wrapping: (String, String),204205 executing_command: Arc<Mutex<()>>,206207 next_id: u32,208 pub(crate) free_list: Vec<u32>,209210 pub nix_system: String,211}212213/// Discover inter-message repl delimiter214const REPL_DELIMITER: &str = "\"FLEET_MAGIC_REPL_DELIMITER\"";215/// Discover formatting around strings216const TRAIN_STRING: &str = "\"TRAIN_STRING\"";217/// Discover formatting around numbers218const TRAIN_NUMBER: &str = "13141516";219// Other types of formatting are not discovered, because they are not used, JSON serialization is used instead220// Techically, number training is also not required, because numbers can be converted to string too...221// Eh, I'll remove it later.222223impl NixSessionInner {224 pub(crate) async fn new(225 flake: &OsStr,226 extra_args: impl IntoIterator<Item = &OsStr>,227 nix_system: String,228 fail_fast: bool,229 ) -> Result<Self> {230 let mut cmd = Command::new("nix");231 cmd.arg("repl")232 .args(["--option", "pure-eval", "true"])233 .arg(flake)234 .arg("--log-format")235 .arg("internal-json");236 if !fail_fast {237 cmd.arg("--keep-going");238 }239 for arg in extra_args {240 cmd.arg(arg);241 }242 cmd.stdin(Stdio::piped());243 cmd.stdout(Stdio::piped());244 cmd.stderr(Stdio::piped());245 let cmd = cmd.spawn()?;246 let stdout = cmd.stdout.unwrap();247 let stderr = cmd.stderr.unwrap();248 let mut out = OutputHandler::new(stdout, stderr);249 let mut stdin = cmd.stdin.unwrap();250 // Standard repl hello doesn't work with internal-json logger251 stdin.write_all(REPL_DELIMITER.as_bytes()).await?;252 stdin.write_all(b"\n").await?;253 stdin.flush().await?;254 let nix_handler = NixHandler::default();255 let mut full_delimiter = None;256 let mut errors = vec![];257 while let Some(line) = out.next().await {258 let line = match line {259 OutputLine::Out(o) => o,260 OutputLine::Err(_e) => {261 // Handle startup errors, but skip repl hello?262 errors.push(_e);263 continue;264 }265 };266 if line.contains(REPL_DELIMITER) {267 debug!("discovered repl delimiter with added colors: {line}");268 full_delimiter = Some(line.to_owned());269 break;270 }271 }272 let Some(full_delimiter) = full_delimiter else {273 for e in errors {274 error!("{e}");275 }276 return Err(Error::SessionInit("failed to discover delimiter"));277 };278 let mut res = Self {279 full_delimiter,280 nix_handler: ClonableHandler::new(nix_handler),281 out,282 stdin,283 string_wrapping: Default::default(),284 number_wrapping: Default::default(),285286 executing_command: Arc::new(Mutex::new(())),287288 next_id: 0,289 free_list: vec![],290291 nix_system,292 };293 res.train().await?;294 Ok(res)295 }296 async fn train(&mut self) -> Result<()> {297 {298 let full_string = self299 .execute_expression_raw(TRAIN_STRING, &mut NoopHandler)300 .await?;301 let string_offset = full_string.find(TRAIN_STRING).expect("contained");302 let string_prefix = &full_string[..string_offset];303 let string_suffix = &full_string[string_offset + TRAIN_STRING.len()..];304 self.string_wrapping = (string_prefix.to_owned(), string_suffix.to_owned());305 }306 {307 let full_number = self308 .execute_expression_raw(TRAIN_NUMBER, &mut NoopHandler)309 .await?;310 let number_offset = full_number.find(TRAIN_NUMBER).expect("contained");311 let number_prefix = &full_number[..number_offset];312 let number_suffix = &full_number[number_offset + TRAIN_NUMBER.len()..];313 self.number_wrapping = (number_prefix.to_owned(), number_suffix.to_owned());314 }315 Ok(())316 }317 async fn send_command(&mut self, cmd: impl AsRef<[u8]>) -> Result<()> {318 if tracing::enabled!(Level::DEBUG) && cmd.as_ref() != REPL_DELIMITER.as_bytes() {319 let cmd_str = String::from_utf8_lossy(cmd.as_ref());320 tracing::debug!("{cmd_str}");321 };322 self.stdin.write_all(cmd.as_ref()).await?;323 self.stdin.write_all(b"\n").await?;324 Ok(())325 }326 async fn read_until_delimiter(&mut self, err_handler: &mut dyn Handler) -> Result<String> {327 let mut out = String::new();328 while let Some(line) = self.out.next().await {329 let line = match line {330 OutputLine::Out(out) => out,331 OutputLine::Err(err) => {332 err_handler.handle_line(&err);333 continue;334 }335 };336 if line == self.full_delimiter {337 return Ok(out);338 }339 if !out.is_empty() {340 out.push('\n');341 }342 out.push_str(&line);343 }344 Err(Error::MissingDelimiter)345 }346 pub(crate) async fn execute_expression_number(347 &mut self,348 expr: impl AsRef<[u8]>,349 ) -> Result<u64> {350 let num = self.number_wrapping.clone();351 let n = self.execute_expression_wrapping(expr, &num).await?;352 n.parse::<u64>().map_err(Error::Int)353 }354 async fn execute_expression_string(&mut self, expr: impl AsRef<[u8]>) -> Result<String> {355 // builtins.toJSON escapes some thing in incorrect way, e.g escaped "$" in "\${" is being outputed as "\$",356 // while this escape should be removed as it is intended for nix itself, not for json output.357 //358 // This regex only allows \$ in the beginning of the string, it is easier to implement correctly.359 // TODO: Add peg parser for nix-produced JSON?..360 let regex = regex::Regex::new(r#"(?<prefix>[: {,\[]\\")\\\$"#).expect("fixup json");361362 let num = self.string_wrapping.clone();363 let n = self.execute_expression_wrapping(expr, &num).await?;364 let n = regex.replace_all(&n, "$prefix$$");365 let str: String = serde_json::from_str(&n)?;366 Ok(str)367 }368 pub(crate) async fn execute_expression_to_json<V: DeserializeOwned>(369 &mut self,370 expr: impl AsRef<[u8]>,371 ) -> Result<V> {372 let mut fexpr = b"builtins.toJSON (".to_vec();373 fexpr.extend_from_slice(expr.as_ref());374 fexpr.push(b')');375376 Ok(serde_json::from_str(377 &self.execute_expression_string(fexpr).await?,378 )?)379 }380 async fn execute_expression_wrapping(381 &mut self,382 expr: impl AsRef<[u8]>,383 wrapping: &(String, String),384 ) -> Result<String> {385 let mut nix_handler = self.nix_handler.clone();386 let mut collected = ErrorCollector::new(&mut nix_handler);387 let res = self.execute_expression_raw(expr, &mut collected).await?;388 if res.is_empty() {389 collected.finish()?;390 return Err(Error::ExpectedOutput);391 } else {392 collected.flush()393 };394 let Some(res) = res.strip_prefix(&wrapping.0) else {395 return Err(Error::InvalidType);396 };397 let Some(res) = res.strip_suffix(&wrapping.1) else {398 return Err(Error::InvalidType);399 };400 Ok(res.to_owned())401 }402 async fn execute_expression_empty(&mut self, expr: impl AsRef<[u8]>) -> Result<()> {403 let mut nix_handler = self.nix_handler.clone();404 let mut collected = ErrorCollector::new(&mut nix_handler);405 let v = self.execute_expression_raw(expr, &mut collected).await?;406 collected.finish()?;407 if !v.is_empty() {408 return Err(Error::UnexpectedOutput);409 }410 Ok(())411 }412 pub(crate) async fn execute_expression_raw(413 &mut self,414 expr: impl AsRef<[u8]>,415 err_handler: &mut dyn Handler,416 ) -> Result<String> {417 // Prevent two commands from being executed in parallel, messing with each other.418 let _lock = self.executing_command.clone();419 let _guard = _lock.lock().await;420421 self.send_command(expr).await?;422 // It will be echoed423 self.send_command(REPL_DELIMITER).await?;424 self.read_until_delimiter(err_handler).await425 }426 pub(crate) async fn execute_assign(&mut self, expr: impl AsRef<str>) -> Result<u32> {427 let id = self.allocate_id();428 self.execute_expression_empty(format!("sess_field_{id} = {}", expr.as_ref()))429 .await?;430 Ok(id)431 }432433 /// Id should be immediately used434 fn allocate_id(&mut self) -> u32 {435 if let Some(free) = self.free_list.pop() {436 free437 } else {438 let v = self.next_id;439 self.next_id += 1;440 v441 }442 }443 // Nix has no way to deallocate variable, yet GC will correct everything not reachable.444 // async fn free_id(&mut self, id: u32) -> Result<()> {445 // self.execute_expression_empty(format!("sess_field_{id} = null"))446 // .await?;447 // self.free_list.push(id);448 // Ok(())449 // }450}crates/nix-eval/src/util.rsdiffbeforeafterboth--- a/crates/nix-eval/src/util.rs
+++ b/crates/nix-eval/src/util.rs
@@ -3,7 +3,7 @@
use anyhow::bail;
use tracing::{debug, warn};
-use crate::{nix_go_json, Value};
+use crate::{Value, nix_go_json};
#[tracing::instrument(level = "info", skip(val))]
pub async fn assert_warn(action: &str, val: &Value) -> anyhow::Result<()> {
crates/nix-eval/src/value.rsdiffbeforeafterboth--- a/crates/nix-eval/src/value.rs
+++ b/crates/nix-eval/src/value.rs
@@ -1,9 +1,9 @@
use std::{collections::HashMap, fmt, path::PathBuf, sync::Arc};
use better_command::NixHandler;
-use serde::{de::DeserializeOwned, Serialize};
+use serde::{Serialize, de::DeserializeOwned};
-use crate::{macros::NixExprBuilder, nix_go, Error, NixBuildBatch, NixSession, Result};
+use crate::{Error, NixBuildBatch, NixSession, Result, macros::NixExprBuilder, nix_go};
#[derive(Clone)]
pub enum Index {
crates/nix-native-eval/Cargo.tomldiffbeforeafterboth--- a/crates/nix-native-eval/Cargo.toml
+++ /dev/null
@@ -1,10 +0,0 @@
-[package]
-name = "nix-native-eval"
-version.workspace = true
-edition.workspace = true
-rust-version.workspace = true
-
-[dependencies]
-anyhow.workspace = true
-
-nixrs = { git = "https://github.com/Anillc/nixrs", version = "0.1.0" }
crates/nix-native-eval/src/lib.rsdiffbeforeafterboth--- a/crates/nix-native-eval/src/lib.rs
+++ /dev/null
@@ -1,11 +0,0 @@
-use anyhow::Result;
-use nixrs::{State, Store};
-
-pub fn init() -> Result<()> {
- nixrs::init()?;
- let store = Store::new("daemon")?;
- let state = State::new(store)?;
- let _ = state;
-
- Ok(())
-}
crates/nixlike/fuzz/Cargo.tomldiffbeforeafterboth--- a/crates/nixlike/fuzz/Cargo.toml
+++ b/crates/nixlike/fuzz/Cargo.toml
@@ -4,7 +4,7 @@
version = "0.0.0"
authors = ["Automatically generated"]
publish = false
-edition = "2021"
+edition = "2024"
[package.metadata]
cargo-fuzz = true
crates/nixlike/src/de_impl.rsdiffbeforeafterboth--- a/crates/nixlike/src/de_impl.rs
+++ b/crates/nixlike/src/de_impl.rs
@@ -2,8 +2,8 @@
use linked_hash_map::LinkedHashMap;
use serde::{
+ Deserializer,
de::{self, MapAccess, SeqAccess},
- Deserializer,
};
use crate::{Error, Value};
@@ -28,11 +28,12 @@
where
K: de::DeserializeSeed<'de>,
{
- if let Some((k, v)) = self.iter.next() {
- let _ = self.value.insert(v);
- Ok(Some(seed.deserialize(Value::String(k))?))
- } else {
- Ok(None)
+ match self.iter.next() {
+ Some((k, v)) => {
+ let _ = self.value.insert(v);
+ Ok(Some(seed.deserialize(Value::String(k))?))
+ }
+ _ => Ok(None),
}
}
@@ -62,10 +63,9 @@
where
T: de::DeserializeSeed<'de>,
{
- if let Some(v) = self.iter.next() {
- Ok(Some(seed.deserialize(v)?))
- } else {
- Ok(None)
+ match self.iter.next() {
+ Some(v) => Ok(Some(seed.deserialize(v)?)),
+ _ => Ok(None),
}
}
}
crates/nixlike/src/se_impl.rsdiffbeforeafterboth--- a/crates/nixlike/src/se_impl.rs
+++ b/crates/nixlike/src/se_impl.rs
@@ -2,11 +2,11 @@
use linked_hash_map::LinkedHashMap;
use serde::{
+ Serializer,
ser::{
self, SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant, SerializeTuple,
SerializeTupleStruct, SerializeTupleVariant,
},
- Serializer,
};
use crate::{Error, Value};
@@ -90,9 +90,7 @@
fn end(self) -> Result<Self::Ok, Self::Error> {
Ok(Value::Object(
- vec![(self.0, Value::Array(self.1 .0))]
- .into_iter()
- .collect(),
+ vec![(self.0, Value::Array(self.1.0))].into_iter().collect(),
))
}
}
flake.lockdiffbeforeafterboth--- a/flake.lock
+++ b/flake.lock
@@ -2,11 +2,11 @@
"nodes": {
"crane": {
"locked": {
- "lastModified": 1739936662,
- "narHash": "sha256-x4syUjNUuRblR07nDPeLDP7DpphaBVbUaSoeZkFbGSk=",
+ "lastModified": 1750266157,
+ "narHash": "sha256-tL42YoNg9y30u7zAqtoGDNdTyXTi8EALDeCB13FtbQA=",
"owner": "ipetkov",
"repo": "crane",
- "rev": "19de14aaeb869287647d9461cbd389187d8ecdb7",
+ "rev": "e37c943371b73ed87faf33f7583860f81f1d5a48",
"type": "github"
},
"original": {
@@ -22,11 +22,11 @@
]
},
"locked": {
- "lastModified": 1738453229,
- "narHash": "sha256-7H9XgNiGLKN1G1CgRh0vUL4AheZSYzPm+zmZ7vxbJdo=",
+ "lastModified": 1749398372,
+ "narHash": "sha256-tYBdgS56eXYaWVW3fsnPQ/nFlgWi/Z2Ymhyu21zVM98=",
"owner": "hercules-ci",
"repo": "flake-parts",
- "rev": "32ea77a06711b758da0ad9bd6a844c5740a87abd",
+ "rev": "9305fe4e5c2a6fcf5ba6a3ff155720fbe4076569",
"type": "github"
},
"original": {
@@ -37,16 +37,16 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1740339700,
- "narHash": "sha256-cbrw7EgQhcdFnu6iS3vane53bEagZQy/xyIkDWpCgVE=",
+ "lastModified": 1750895632,
+ "narHash": "sha256-EPZWiRmaSTxoBArK5dQyRlSNVLXiBt2hmsYIPgMf3zk=",
"owner": "nixos",
"repo": "nixpkgs",
- "rev": "04ef94c4c1582fd485bbfdb8c4a8ba250e359195",
+ "rev": "6ac57ce7fee0d80226095a57ccb7519855ad7c5e",
"type": "github"
},
"original": {
"owner": "nixos",
- "ref": "release-24.11",
+ "ref": "release-25.05",
"repo": "nixpkgs",
"type": "github"
}
@@ -68,11 +68,11 @@
]
},
"locked": {
- "lastModified": 1740277845,
- "narHash": "sha256-NNU0CdiaSbAeZ8tpDG4aFi9qtcdlItRvk8Xns9oBrVU=",
+ "lastModified": 1750819193,
+ "narHash": "sha256-XvkupGPZqD54HuKhN/2WhbKjAHeTl1UEnWspzUzRFfA=",
"owner": "oxalica",
"repo": "rust-overlay",
- "rev": "f933070c29f9c1c5457447a51903f27f76ebb519",
+ "rev": "1ba3b9c59b68a4b00156827ad46393127b51b808",
"type": "github"
},
"original": {
@@ -103,11 +103,11 @@
]
},
"locked": {
- "lastModified": 1744961264,
- "narHash": "sha256-aRmUh0AMwcbdjJHnytg1e5h5ECcaWtIFQa6d9gI85AI=",
+ "lastModified": 1749194973,
+ "narHash": "sha256-eEy8cuS0mZ2j/r/FE0/LYBSBcIs/MKOIVakwHVuqTfk=",
"owner": "numtide",
"repo": "treefmt-nix",
- "rev": "8d404a69efe76146368885110f29a2ca3700bee6",
+ "rev": "a05be418a1af1198ca0f63facb13c985db4cb3c5",
"type": "github"
},
"original": {
flake.nixdiffbeforeafterboth--- a/flake.nix
+++ b/flake.nix
@@ -2,7 +2,7 @@
description = "NixOS cluster configuration management";
inputs = {
- nixpkgs.url = "github:nixos/nixpkgs/release-24.11";
+ nixpkgs.url = "github:nixos/nixpkgs/release-25.05";
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
@@ -156,7 +156,7 @@
bacon
nil
rustPlatform.bindgenHook
- nixVersions.nix_2_22
+ # nixVersions.nix_2_22
];
environment.PROTOC = "${pkgs.protobuf}/bin/protoc";
};
rust-toolchain.tomldiffbeforeafterboth--- a/rust-toolchain.toml
+++ b/rust-toolchain.toml
@@ -1,3 +1,3 @@
[toolchain]
-channel = "1.85.0"
+channel = "1.86.0"
components = ["rustfmt", "clippy", "rust-analyzer", "rust-src"]
rustfmt.tomldiffbeforeafterboth--- a/rustfmt.toml
+++ b/rustfmt.toml
@@ -1,3 +1,3 @@
hard_tabs = true
-imports_granularity = "Crate"
-group_imports = "StdExternalCrate"
+# imports_granularity = "Crate"
+# group_imports = "StdExternalCrate"