Files
core/test/dever-cli-tests/shared_toolchain_extensions.rs

347 lines
12 KiB
Rust

use super::*;
use dever_cli::libs::Ecosystem;
use dever_cli::toolchain::{
BuildTarget, Extension, ExtensionId, ExtensionKind, ExtensionSource, SignedResources,
artifact_get_optional, prepare_extension_with,
};
use std::sync::atomic::AtomicUsize;
fn id(ecosystem: Ecosystem) -> ExtensionId {
ExtensionId {
kind: ExtensionKind::Runtime(ecosystem),
target: BuildTarget::host().unwrap(),
}
}
fn logical_path(id: &ExtensionId) -> String {
let ExtensionKind::Runtime(ecosystem) = &id.kind else {
panic!("runtime fixture")
};
format!(
"runtime/{}/{}/runtime.pack",
ecosystem.as_str(),
id.target.platform()
)
}
struct Source {
requests: AtomicUsize,
fail: bool,
}
impl Source {
fn new(fail: bool) -> Self {
Self {
requests: AtomicUsize::new(0),
fail,
}
}
}
impl ExtensionSource for Source {
fn open(&self, version: &Version, extension: &ExtensionId) -> Result<Box<dyn Read>, String> {
self.requests.fetch_add(1, Ordering::Relaxed);
if self.fail {
return Err("fixture extension download failed".into());
}
let mut tar = tar::Builder::new(Vec::new());
let mut header = tar::Header::new_ustar();
header.set_size(version.as_str().len() as u64);
header.set_mode(0o644);
header.set_cksum();
tar.append_data(
&mut header,
logical_path(extension),
version.as_str().as_bytes(),
)
.unwrap();
let mut encoder = zstd::stream::write::Encoder::new(Vec::new(), 1).unwrap();
encoder.include_checksum(true).unwrap();
encoder.write_all(&tar.into_inner().unwrap()).unwrap();
Ok(Box::new(std::io::Cursor::new(encoder.finish().unwrap())))
}
}
fn release(layout: &Layout, key: &Ed25519KeyPair, version: &str) {
write_release(
layout,
key,
version,
&layout.root().join("dispatch"),
true,
true,
);
let directory = layout.downloads().join(version);
let path = directory.join("manifest.json");
let mut manifest: ReleaseManifest = serde_json::from_slice(&fs::read(&path).unwrap()).unwrap();
for ecosystem in [Ecosystem::Pip, Ecosystem::Npm] {
let id = id(ecosystem);
manifest.extensions.push(Extension {
kind: id.kind.clone(),
target: id.target,
artifacts: vec![Artifact {
path: logical_path(&id),
bytes: version.len() as u64,
sha256: hex(&Sha256::digest(version.as_bytes())),
}],
});
}
let bytes = serde_json::to_vec(&manifest).unwrap();
fs::write(path, &bytes).unwrap();
fs::write(
directory.join("manifest.sig"),
hex(key.sign(&bytes).as_ref()),
)
.unwrap();
}
fn machine() -> (TemporaryDirectory, Layout, MachineManager, Ed25519KeyPair) {
let root = TemporaryDirectory::new();
let layout = Layout::new(root.path().join("machine"));
layout.initialize().unwrap();
let key = signing_key();
fs::write(
layout.state().join("trusted-release-key"),
hex(key.public_key().as_ref()),
)
.unwrap();
let manager = MachineManager::new(layout.clone());
(root, layout, manager, key)
}
#[test]
fn signed_extension_preparation_is_shared_immutable_and_concurrent() {
let (_root, layout, manager, key) = machine();
release(&layout, &key, "1.0.0");
let version = manager.install_requested("1.0.0").unwrap();
let extension = id(Ecosystem::Pip);
let logical = logical_path(&extension);
let resources = SignedResources::load(&layout, &version).unwrap();
assert!(
resources
.read(&logical)
.unwrap_err()
.contains("dever lib install")
);
let source = Source::new(false);
std::thread::scope(|scope| {
for _ in 0..2 {
scope.spawn(|| prepare_extension_with(&layout, &version, &extension, &source).unwrap());
}
});
assert_eq!(source.requests.load(Ordering::Relaxed), 1);
assert_eq!(resources.read(&logical).unwrap(), b"1.0.0");
let path = resources.path(&logical).unwrap();
assert!(path.starts_with(layout.extensions()));
assert!(!layout.versions().join("1.0.0").join(&logical).exists());
fs::write(&path, b"other").unwrap();
assert!(resources.read(&logical).unwrap_err().contains("integrity"));
assert!(
prepare_extension_with(&layout, &version, &extension, &source)
.unwrap_err()
.contains("integrity")
);
assert_eq!(source.requests.load(Ordering::Relaxed), 1);
}
#[test]
fn signed_resources_cross_machine_roots_without_sharing_the_cache() {
let (root, layout, manager, key) = machine();
release(&layout, &key, "1.0.0");
let version = manager.install_requested("1.0.0").unwrap();
let mut daemon = TestDaemon::start(&layout);
let client = Layout::new(root.path().join("client"));
client.initialize().unwrap();
fs::copy(
layout.state().join("trusted-release-key"),
client.state().join("trusted-release-key"),
)
.unwrap();
let source = layout.versions().join(version.as_str());
let manifest: ReleaseManifest =
serde_json::from_slice(&fs::read(source.join("manifest.json")).unwrap()).unwrap();
for path in manifest
.artifacts
.iter()
.map(|artifact| artifact.path.as_str())
.chain(["manifest.json", "manifest.sig"])
{
let destination = client.downloads().join(version.as_str()).join(path);
fs::create_dir_all(destination.parent().unwrap()).unwrap();
fs::copy(source.join(path), destination).unwrap();
}
MachineManager::new(client.clone())
.install_requested(version.as_str())
.unwrap();
fs::hard_link(layout.service_socket(), client.service_socket()).unwrap();
fs::copy(layout.service_token(), client.service_token()).unwrap();
let resources = SignedResources::load(&client, &version).unwrap();
let extension = id(Ecosystem::Pip);
let logical = logical_path(&extension);
assert!(
resources
.read_shared(&logical)
.unwrap_err()
.contains("not installed")
);
assert!(!layout.extensions().exists());
let source = Source::new(false);
prepare_extension_with(&layout, &version, &extension, &source).unwrap();
assert_eq!(resources.read_shared(&logical).unwrap(), b"1.0.0");
assert_eq!(resources.read_shared(&logical).unwrap(), b"1.0.0");
assert_eq!(source.requests.load(Ordering::Relaxed), 1);
assert!(!client.extensions().exists());
assert!(
resources
.read(&logical)
.unwrap_err()
.contains("not installed")
);
assert!(
resources
.read_shared(&logical_path(&id(Ecosystem::Npm)))
.unwrap_err()
.contains("not installed")
);
assert!(
resources
.read_shared("../../state/service-token")
.unwrap_err()
.contains("does not declare")
);
let path = SignedResources::load(&layout, &version)
.unwrap()
.path(&logical)
.unwrap();
fs::write(&path, b"other").unwrap();
assert!(
resources
.read_shared(&logical)
.unwrap_err()
.contains("integrity")
);
daemon.stop();
assert!(resources.read_shared(&logical).is_err());
}
#[test]
fn signed_resource_ipc_rejects_unbound_receipts_catalogs_and_paths() {
let (_root, layout, manager, key) = machine();
release(&layout, &key, "1.0.0");
let version = manager.install_requested("1.0.0").unwrap();
prepare_extension_with(&layout, &version, &id(Ecosystem::Pip), &Source::new(false)).unwrap();
let _daemon = TestDaemon::start(&layout);
let token = fs::read_to_string(layout.service_token()).unwrap();
let catalog = hex(&Sha256::digest(
fs::read(layout.versions().join("1.0.0/manifest.json")).unwrap(),
));
let request = serde_json::json!({"version":"1.0.0", "catalog":catalog,
"extension":id(Ecosystem::Pip), "artifact":{"sha256":hex(&Sha256::digest(b"1.0.0")), "bytes":5}});
for field in ["catalog", "artifact", "extension", "path"] {
let mut invalid = request.clone();
invalid[field] = match field {
"catalog" => serde_json::json!("0".repeat(64)),
"artifact" => serde_json::json!({"sha256":"0".repeat(64),"bytes":u64::MAX}),
"extension" => serde_json::Value::Null,
_ => serde_json::json!("../../state/service-token"),
};
let result = raw_operation(
&layout,
token.trim(),
serde_json::json!({"resource_get":invalid}),
);
assert_eq!(result["ok"], false, "{field}: {result}");
}
}
#[test]
fn update_prepares_only_installed_extensions_and_preserves_active_on_failure() {
let (_root, layout, manager, key) = machine();
for version in ["1.0.0", "2.0.0"] {
release(&layout, &key, version);
}
let old = manager.install_requested("1.0.0").unwrap();
let extension = id(Ecosystem::Pip);
prepare_extension_with(&layout, &old, &extension, &Source::new(false)).unwrap();
fs::write(layout.downloads().join("latest"), "2.0.0").unwrap();
let journal = fs::read(layout.state().join("active-version")).unwrap();
assert!(
manager
.update_with(&Source::new(true))
.unwrap_err()
.contains("download failed")
);
assert_eq!(
fs::read(layout.state().join("active-version")).unwrap(),
journal
);
assert_eq!(
SignedResources::load(&layout, &old)
.unwrap()
.read(&logical_path(&extension))
.unwrap(),
b"1.0.0"
);
let source = Source::new(false);
let new = manager.update_with(&source).unwrap();
assert_eq!(new.as_str(), "2.0.0");
assert_eq!(source.requests.load(Ordering::Relaxed), 1);
let resources = SignedResources::load(&layout, &new).unwrap();
assert_eq!(resources.read(&logical_path(&extension)).unwrap(), b"2.0.0");
assert!(
resources
.read(&logical_path(&id(Ecosystem::Npm)))
.unwrap_err()
.contains("not installed")
);
assert_eq!(manager.active_version().unwrap(), Some(new));
}
#[test]
fn optional_artifact_ipc_distinguishes_absence_corruption_and_service_failure() {
let (_root, layout, _manager, _key) = machine();
let mut daemon = TestDaemon::start(&layout);
let digest = hex(&Sha256::digest(b"payload"));
assert!(
artifact_get_optional(&layout, &digest, 7)
.unwrap()
.is_none()
);
artifact_put(&layout, b"payload").unwrap();
assert_eq!(
artifact_get_optional(&layout, &digest, 7).unwrap().unwrap(),
b"payload"
);
fs::write(
layout.native_cache().join("artifacts").join(&digest),
b"changed",
)
.unwrap();
assert!(
artifact_get_optional(&layout, &digest, 7)
.unwrap_err()
.contains("SHA-256")
);
daemon.stop();
assert!(artifact_get_optional(&layout, &digest, 7).is_err());
}
#[test]
fn extension_ipc_rejects_caller_paths_and_invalid_typed_identity() {
let (_root, layout, _manager, _key) = machine();
let _daemon = TestDaemon::start(&layout);
let token = fs::read_to_string(layout.service_token()).unwrap();
for selection in [
serde_json::json!({"version":"1.0.0","extension":{"kind":{"type":"runtime","ecosystem":"pip"},"target":"linux-x86_64"},"url":"http://localhost/"}),
serde_json::json!({"version":"../../outside","extension":{"kind":{"type":"target"},"target":"linux-aarch64"}}),
serde_json::json!({"version":"1.0.0","extension":{"kind":{"type":"shell"},"target":"linux-x86_64"}}),
] {
let result = raw_operation(
&layout,
token.trim(),
serde_json::json!({"ensure_extension":selection}),
);
assert_eq!(result["ok"], false, "{result}");
}
}