Skip to content
Open
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
22 changes: 22 additions & 0 deletions src/package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,28 @@ impl Package {
self.get_output_path(name, output_directory),
)));

// Also record the stamped version at a fixed path *inside* the
// zone filesystem. The version otherwise lives only in the
// top-level `oxide.json` metadata, which is readable from the
// global zone (by opening the image tarball) but not from a
// process running inside the zone. Writing it here lets such a
// process read its own version (e.g. Nexus, for `/v1/version`).
//
// This mirrors the top-level `VERSION` file that `Tarball`
// outputs already carry. The parent directories must be added
// before the file, as zone images require parents before their
// children.
let system_version_path = Utf8Path::new("/var/oxide/system-version");
inputs.0.extend(
zone_get_all_parent_inputs(system_version_path.parent().unwrap())?
.into_iter()
.map(BuildInput::AddDirectory),
);
inputs.0.push(BuildInput::AddInMemoryFile {
dst_path: zone_archive_path(system_version_path)?,
contents: version.to_string(),
});

// Add the package to "itself", but as a stamped version.
//
// We jump through some hoops to avoid modifying the archive
Expand Down
43 changes: 43 additions & 0 deletions tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,49 @@ mod test {
assert!(ents.next().is_none());
}

// Stamping a zone package records the version at a fixed path inside the
// zone filesystem (in addition to the top-level `oxide.json`), so a
// process running in the zone can read its own version.
#[tokio::test(flavor = "multi_thread")]
async fn test_stamp_zone_writes_system_version() {
let cfg = config::parse("tests/service-b/cfg.toml").unwrap();
let package = cfg.packages.get(&MY_SERVICE_PACKAGE).unwrap();

let out = camino_tempfile::tempdir().unwrap();
let build_config = BuildConfig::default();
package
.create(&MY_SERVICE_PACKAGE, out.path(), &build_config)
.await
.unwrap();

let expected_semver = semver::Version::new(4, 5, 6);
let path = package
.stamp(&MY_SERVICE_PACKAGE, out.path(), &expected_semver)
.await
.unwrap();
assert!(path.exists());

let gzr = flate2::read::GzDecoder::new(File::open(path).unwrap());
let mut archive = Archive::new(gzr);
let mut ents = archive.entries().unwrap();

// The OMICRON1(5) format requires oxide.json to be the first entry.
assert_eq!("oxide.json", ents.next_path());

// Somewhere later, the in-zone version file should appear with the
// stamped version as its contents.
let mut found = None;
for entry in ents {
let mut entry = entry.unwrap();
if entry_path(&entry) == "root/var/oxide/system-version" {
let mut s = String::new();
entry.read_to_string(&mut s).unwrap();
found = Some(s);
}
}
assert_eq!(found.as_deref(), Some(expected_semver.to_string().as_str()));
}

// Tests a rust package being placed into a non-Zone image.
//
// This is used for building packages that exist in the Global Zone,
Expand Down
Loading