Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/buildomat/jobs/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,20 @@ while ! curl -sSf "$OXIDE_HOST/v1/ping" --resolve "$OXIDE_RESOLVE" --cacert "$E2
retry=$((retry + 1))
done

EXPECTED_SYSTEM_VERSION=$(<out/deploy-system-version)
VERSION_RESPONSE=$(
/usr/oxide/oxide \
--resolve "$OXIDE_RESOLVE" \
--cacert "$E2E_TLS_CERT" \
api /v1/version
)
EXPECTED_SYSTEM_VERSION_FIELD="\"system_version\": \"$EXPECTED_SYSTEM_VERSION\""
if [[ "$VERSION_RESPONSE" != *"$EXPECTED_SYSTEM_VERSION_FIELD"* ]]; then
echo "expected system version $EXPECTED_SYSTEM_VERSION; response:"
echo "$VERSION_RESPONSE"
exit 1
fi

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This passes, so the whole thing works in a basic sense. Neat.

/usr/oxide/oxide --resolve "$OXIDE_RESOLVE" --cacert "$E2E_TLS_CERT" \
project create --name images --description "some images"
/usr/oxide/oxide \
Expand Down
12 changes: 12 additions & 0 deletions .github/buildomat/jobs/package.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,17 @@ ptime -m cargo run --locked --release --bin omicron-package -- \
-t test target create -p dev
ptime -m cargo run --locked --release --bin omicron-package -- \
-t test package

# Exercise the same stamped-zone path used by releng while giving the deploy
# job a distinctive value to verify through Nexus. `install` reads packages
# from their unversioned output paths, so replace the Nexus archive with the
# stamped output before assembling package.tar.gz.
DEPLOY_SYSTEM_VERSION="0.0.0-deploy-test"
ptime -m cargo run --locked --release --bin omicron-package -- \
--target test stamp nexus "$DEPLOY_SYSTEM_VERSION"
cp out/versioned/nexus.tar.gz out/nexus.tar.gz
printf '%s\n' "$DEPLOY_SYSTEM_VERSION" >out/deploy-system-version

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My tentative understanding is that it should be ok to stamp here because the only thing that reads this archive is the helios / deploy CI job, i.e., this shouldn't conflict with any actual release stamping business.

mapfile -t packages \
< <(cargo run --locked --release --bin omicron-package -- -t test list-outputs)

Expand All @@ -55,6 +66,7 @@ mkdir tests

files=(
.github/buildomat/ci-env.sh
out/deploy-system-version
out/target/test
out/npuzone/*
package-manifest.toml
Expand Down
3 changes: 1 addition & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1088,6 +1088,11 @@ opt-level = 3
# dependencies. If you want to use those, uncomment one of these blocks.
#
[patch.crates-io]
# PROTOTYPE ONLY: use the unpublished in-zone system-version change
# end-to-end (see .claude/notes/2026-07-02-full-system-version-in-zone.md).
# Revert before merge; the real change ships as a published
# omicron-zone-package release + version bump above.
omicron-zone-package = { git = "https://github.com/oxidecomputer/omicron-package.git", rev = "b3a1c66d2abf1c68f1ecf500a5de6df6515b7f5e" }
# diesel = { path = "../../diesel/diesel" }
# drift = { path = "../drift" }
# dropshot = { path = "../dropshot/dropshot" }
Expand Down
80 changes: 79 additions & 1 deletion common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,46 @@ pub fn format_time_delta(time_delta: chrono::TimeDelta) -> String {
/// the future.
///
/// The releng build process appends build metadata to this to produce the full
/// version string; the `/v1/version` API endpoint returns it as-is.
/// version string. It is the compile-time fallback for [`system_version`],
/// which prefers the full stamped string when running in a deployed zone.
pub const SYSTEM_VERSION: semver::Version = semver::Version::new(22, 0, 0);

/// Path, inside a deployed zone, of the file holding the full stamped system
/// version string (e.g. `21.0.0-0.ci+git0abc1234def`).
///
/// `omicron-package stamp` writes the version into each zone image's top-level
/// `oxide.json` metadata, but that metadata is only readable from the global
/// zone (by re-opening the image tarball); a process running *inside* a zone
/// cannot see it. To make the full version available to Nexus at runtime, the
/// stamp step also writes it to this path within the zone filesystem. The file
/// is absent in dev/test builds and in unstamped packages.
const SYSTEM_VERSION_PATH: &str = "/var/oxide/system-version";

/// The full version of the running system software, as served by `/v1/version`.
///
/// Returns the stamped version string from [`SYSTEM_VERSION_PATH`] when present
/// (a deployed, stamped zone), otherwise the compile-time [`SYSTEM_VERSION`]
/// core. The result is read once and cached: a system update replaces the zone
/// image and restarts the process, so the file cannot change under a running
/// server.
pub fn system_version() -> &'static semver::Version {
static VERSION: std::sync::OnceLock<semver::Version> =
std::sync::OnceLock::new();
VERSION.get_or_init(|| {
read_system_version(std::path::Path::new(SYSTEM_VERSION_PATH))
})
}

/// Read and parse the stamped system version from `path`, falling back to
/// [`SYSTEM_VERSION`] when the file is absent or does not contain a valid
/// semver. Split out from [`system_version`] (which caches) to be testable.
fn read_system_version(path: &std::path::Path) -> semver::Version {
match std::fs::read_to_string(path) {
Ok(contents) => contents.trim().parse().unwrap_or(SYSTEM_VERSION),
Err(_) => SYSTEM_VERSION,
}
}

pub const OMICRON_DPD_TAG: &str = "omicron";

/// A wrapper struct that does nothing other than elide the inner value from
Expand Down Expand Up @@ -187,3 +224,44 @@ impl std::fmt::Debug for BytesToHexDebug<'_> {
Ok(())
}
}

#[cfg(test)]
mod test {
use super::{SYSTEM_VERSION, read_system_version};
use camino_tempfile::NamedUtf8TempFile;
use std::io::Write;

fn write_version_file(contents: &str) -> NamedUtf8TempFile {
let mut file = NamedUtf8TempFile::new().unwrap();
file.write_all(contents.as_bytes()).unwrap();
file.flush().unwrap();
file
}

#[test]
fn system_version_falls_back_when_file_absent() {
let path = camino::Utf8Path::new("/nonexistent/system-version");
assert_eq!(read_system_version(path.as_std_path()), SYSTEM_VERSION);
}

#[test]
fn system_version_reads_full_stamped_string() {
// The stamped string carries prerelease and build metadata that the
// compile-time SYSTEM_VERSION core lacks. The trailing newline tests
// that whitespace around the version is trimmed.
const STAMPED_VERSION: &str = "21.0.0-0.ci+git0abc1234def";
let expected: semver::Version = STAMPED_VERSION.parse().unwrap();
let file = write_version_file(&format!("{STAMPED_VERSION}\n"));
let version = read_system_version(file.path().as_std_path());
assert_eq!(version, expected);
}

#[test]
fn system_version_falls_back_on_unparseable_file() {
let file = write_version_file("not a version");
assert_eq!(
read_system_version(file.path().as_std_path()),
SYSTEM_VERSION
);
}
}
2 changes: 1 addition & 1 deletion nexus/external-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ pub trait NexusExternalApi {
) -> Result<HttpResponseOk<latest::system::Version>, HttpError> {
Ok(HttpResponseOk(latest::system::Version {
api_version: latest_version(),
system_version: omicron_common::SYSTEM_VERSION,
system_version: omicron_common::system_version().clone(),
}))
}

Expand Down
Loading