diff --git a/.version b/.version new file mode 100644 index 0000000..f0bb29e --- /dev/null +++ b/.version @@ -0,0 +1 @@ +1.3.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index f0d8cf2..d17b074 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,9 @@ for the up to date details! handshake if `--experimental-nix-client` is passed. - Node evaluations are now cached between wire invocations in flake hives, invalidating old caches with a connection to the local nix daemon. +- Parsing the output generated from `makeHive` now supports reading the schema version in semver + format alongside the previous integer-based system. Currently, it still reports `1` to maintain + backwards compatibility with v1.3.0. ### Changed @@ -43,6 +46,7 @@ for the up to date details! - Building & Pushing derivation outputs now explicitly operate on the `^out` output instead of incorrectly pushing all outputs (`^*`). This results in a roughly 12% speed increase in benchmarking. +- Unknown fields in the hive schema are now ignored rather than rejected. ### Fixed diff --git a/Cargo.lock b/Cargo.lock index 76c5032..55c8a70 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1897,9 +1897,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" @@ -3043,6 +3043,7 @@ dependencies = [ "proc-macro2", "prost", "rand 0.10.2", + "semver", "serde", "serde_json", "sha2 0.11.0", diff --git a/crates/cli/src/cli.rs b/crates/cli/src/cli.rs index cf0f707..83586bc 100644 --- a/crates/cli/src/cli.rs +++ b/crates/cli/src/cli.rs @@ -30,7 +30,7 @@ use std::{ name = "wire", bin_name = "wire", about = "a tool to deploy nixos systems", - version = format!("{}\nDebug: Hive::SCHEMA_VERSION {}", crate_version!(), Hive::SCHEMA_VERSION) + version = format!("{}, supports hives within {}", crate_version!(), *Hive::SCHEMA_VERSION_SEMVER) )] pub struct Cli { #[command(subcommand)] diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index cd8394f..7d390a9 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -41,6 +41,7 @@ sqlx = { version = "0.9", features = ["runtime-tokio", "sqlite"] } zstd = "0.13.3" wire-nix-client = { path = "../nix_client" } memchr = { version = "2", default-features = false } +semver = "1.0.28" [dev-dependencies] tempdir = "0.3" diff --git a/crates/core/src/cache/migrations/20260705061744_semver_schema_version.sql b/crates/core/src/cache/migrations/20260705061744_semver_schema_version.sql new file mode 100644 index 0000000..474f5a8 --- /dev/null +++ b/crates/core/src/cache/migrations/20260705061744_semver_schema_version.sql @@ -0,0 +1,20 @@ +-- migrate schema version to be semver instead of an int + +drop table inspection_cache; +drop table inspection_blobs; + +create table inspection_blobs ( + id integer primary key autoincrement, + json_value blob not null unique, + schema_version text not null +) strict; + +create table inspection_cache ( + store_path_digest blob, + store_path_name text, + hash_sri text, + + blob_id integer references inspection_blobs (id) not null, + + primary key (store_path_digest, store_path_name, hash_sri) +) strict; diff --git a/crates/core/src/cache/mod.rs b/crates/core/src/cache/mod.rs index 7c877da..b5eb596 100644 --- a/crates/core/src/cache/mod.rs +++ b/crates/core/src/cache/mod.rs @@ -174,7 +174,7 @@ impl InspectionCache { ", store_path_digest, hash, - Hive::SCHEMA_VERSION, + &**Hive::SCHEMA_VERSION_STRING, store_path_name ) .fetch_optional(&self.pool) @@ -228,7 +228,7 @@ impl InspectionCache { returning inspection_blobs.id ", json_value, - Hive::SCHEMA_VERSION + &**Hive::SCHEMA_VERSION_STRING ) .fetch_one(&self.pool) .await @@ -438,7 +438,7 @@ where offset 30 )", - Hive::SCHEMA_VERSION + &**Hive::SCHEMA_VERSION_STRING ) .execute(&self.pool) .await?; diff --git a/crates/core/src/hive/mod.rs b/crates/core/src/hive/mod.rs index ffb19b5..25be345 100644 --- a/crates/core/src/hive/mod.rs +++ b/crates/core/src/hive/mod.rs @@ -6,6 +6,7 @@ use nix_compat::flakeref::FlakeRef; use nix_compat::nixhash::NixHash; use node::{Name, Node}; use owo_colors::{OwoColorize, Stream}; +use semver::{Version, VersionReq}; use serde::de::Error; use serde::{Deserialize, Deserializer, Serialize}; use std::collections::HashMap; @@ -14,7 +15,7 @@ use std::fmt::Display; use std::fs; use std::path::PathBuf; use std::str::FromStr; -use std::sync::Arc; +use std::sync::{Arc, LazyLock}; use tracing::{debug, info, instrument}; use crate::cache::InspectionCache; @@ -28,27 +29,88 @@ pub mod node; pub mod plan; pub mod steps; +#[derive(Debug, Eq, PartialEq)] +pub enum SchemaVersion { + Semver(semver::Version), + DeprecatedInteger(u64), +} + #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] -#[serde(deny_unknown_fields)] pub struct Hive { pub nodes: HashMap, #[serde(deserialize_with = "check_schema_version", rename = "_schema")] - pub schema: u32, + pub schema: SchemaVersion, } -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!", - )); +impl Serialize for SchemaVersion { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self { + Self::Semver(version) => serializer.serialize_str(&version.to_string()), + Self::DeprecatedInteger(integer) => serializer.serialize_u64(*integer), + } } - Ok(version) +} + +fn check_schema_version<'de, D: Deserializer<'de>>(d: D) -> Result { + let value = serde_json::Value::deserialize(d)?; + + if value.is_number() { + let number = value.as_u64().ok_or(D::Error::custom( + "failed to read deprecated integer schema version into an u64", + ))?; + + if number != Hive::DEPRECATED_SCHEMA_VERSION { + return Err(D::Error::custom(format!( + "your `makeHive` function can't be read (schema verison {number:?}, required {:?}). please upgrade it or downgrade this binary", + Hive::DEPRECATED_SCHEMA_VERSION + ))); + } + + return Ok(SchemaVersion::DeprecatedInteger(number)); + } + + let semver_string = value.as_str().ok_or(D::Error::custom( + "failed to read schema version into a &str. this is likely a bug you should report", + ))?; + + let version = Version::parse(semver_string).map_err(|err| { + D::Error::custom(format!( + "failed to parse schema semver. this is likely a bug you should report: {err:?}" + )) + })?; + + if !Hive::SCHEMA_VERSION_SEMVER.matches(&version) { + return Err(D::Error::custom(format!( + "your `makeHive`'s version ({version}) did not match {}. please upgrade it or downgrade this binary", + *Hive::SCHEMA_VERSION_SEMVER + ))); + } + + Ok(SchemaVersion::Semver( + Version::parse(semver_string).map_err(|err| { + D::Error::custom(format!( + "failed to parse schema semver. this is likely a bug you should report: {err:?}" + )) + })?, + )) } impl Hive { - pub const SCHEMA_VERSION: u32 = 1; + /// The schema version that was previously used before the semver schema + /// versions where implemented + pub const DEPRECATED_SCHEMA_VERSION: u64 = 1; + + /// Semver version requirement for schemas that this wire binary can read. + pub const SCHEMA_VERSION_SEMVER: LazyLock = LazyLock::new(|| { + VersionReq::parse("^1.0.0").expect("hive version requirement failed to parse") + }); + + pub const SCHEMA_VERSION_STRING: LazyLock = + LazyLock::new(|| Hive::SCHEMA_VERSION_SEMVER.to_string()); #[instrument(skip_all, name = "eval_hive")] pub async fn new_from_path( @@ -330,7 +392,7 @@ mod tests { hive, Hive { nodes, - schema: Hive::SCHEMA_VERSION + schema: SchemaVersion::DeprecatedInteger(Hive::DEPRECATED_SCHEMA_VERSION) } ); } @@ -372,7 +434,7 @@ mod tests { hive, Hive { nodes, - schema: Hive::SCHEMA_VERSION + schema: SchemaVersion::DeprecatedInteger(Hive::DEPRECATED_SCHEMA_VERSION) } ); } @@ -404,7 +466,7 @@ mod tests { hive, Hive { nodes, - schema: Hive::SCHEMA_VERSION + schema: SchemaVersion::DeprecatedInteger(Hive::DEPRECATED_SCHEMA_VERSION) } );