Skip to content
Merged
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
89 changes: 41 additions & 48 deletions crates/wright-driver/src/opy_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ use std::time::Duration;
use flate2::read::GzDecoder;
use sha2::{Digest, Sha256};

const DEFAULT_API_URL: &str = "https://api.github.com/repos/wrightkit/opy-rs/releases/latest";
const DEFAULT_BASE_URL: &str = "https://github.com/wrightkit/opy-rs/releases/download";
const DEFAULT_LATEST_VERSION_URL: &str = "https://releases.wrightkit.dev/opy-rs/latest/version";
const DEFAULT_BASE_URL: &str = "https://releases.wrightkit.dev/opy-rs/releases";
const MAX_DOWNLOAD_BYTES: u64 = 128 * 1024 * 1024;
const PROVIDER_ARCHIVE_EXTENSION: &str = "tar.gz";

Expand Down Expand Up @@ -72,7 +72,7 @@ pub struct ResolvedOpyProvider {
pub struct OpyProviderResolver {
store_dir: PathBuf,
target: Option<String>,
api_url: String,
latest_version_url: String,
base_url: String,
}

Expand All @@ -82,7 +82,7 @@ impl OpyProviderResolver {
Self {
store_dir: store_dir.into(),
target: None,
api_url: DEFAULT_API_URL.to_string(),
latest_version_url: DEFAULT_LATEST_VERSION_URL.to_string(),
base_url: DEFAULT_BASE_URL.to_string(),
}
}
Expand All @@ -97,10 +97,10 @@ impl OpyProviderResolver {
/// Override the release endpoints without changing the artifact layout.
pub fn with_release_urls(
mut self,
api_url: impl Into<String>,
latest_version_url: impl Into<String>,
base_url: impl Into<String>,
) -> Self {
self.api_url = api_url.into();
self.latest_version_url = latest_version_url.into();
self.base_url = base_url.into();
self
}
Expand Down Expand Up @@ -195,7 +195,7 @@ impl OpyProviderResolver {
};
let archive_name = format!("opy-provider-{version}-{target}.{PROVIDER_ARCHIVE_EXTENSION}");
let archive_url = format!(
"{}/v{version}/{archive_name}",
"{}/{version}/{archive_name}",
self.base_url.trim_end_matches('/')
);
let checksum_url = format!("{archive_url}.sha256");
Expand All @@ -210,23 +210,8 @@ impl OpyProviderResolver {
}

fn fetch_latest_version(&self) -> Result<String, OpyProviderError> {
let body = fetch_text(&self.api_url)?;
let value: serde_json::Value = serde_json::from_str(&body).map_err(|error| {
OpyProviderError::download(format!(
"cannot parse the OPY release response from {}: {error}",
self.api_url
))
})?;
let tag = value
.get("tag_name")
.and_then(serde_json::Value::as_str)
.ok_or_else(|| {
OpyProviderError::download(format!(
"the OPY release response from {} has no tag_name",
self.api_url
))
})?;
normalize_version(tag)
let body = fetch_text(&self.latest_version_url)?;
normalize_version(body.trim())
}

fn install_archive(
Expand Down Expand Up @@ -716,28 +701,31 @@ mod tests {
let resolver = OpyProviderResolver::new(&root).with_target(target);
let bytes = archive("2.0.0", target, b"cached");
resolver.install_archive("2.0.0", target, &bytes).unwrap();
let offline = resolver.with_release_urls("http://127.0.0.1:1/latest", "http://127.0.0.1:1");
let offline = resolver.with_release_urls(
"http://127.0.0.1:1/opy-rs/latest/version",
"http://127.0.0.1:1/opy-rs/releases",
);
let resolved = offline.resolve(None).unwrap();
assert_eq!(resolved.version.as_deref(), Some("2.0.0"));
assert_eq!(std::fs::read(resolved.executable).unwrap(), b"cached");
let _ = std::fs::remove_dir_all(root);
}

#[test]
fn missing_provider_bootstraps_from_release_api_and_artifacts() {
fn missing_provider_bootstraps_from_r2_pointer_and_artifacts() {
let root = test_root("bootstrap");
let target = "x86_64-unknown-linux-gnu";
let version = "3.1.4";
let bytes = archive(version, target, b"bootstrapped");
let checksum = format!("{} opy-provider-{version}-{target}.tar.gz\n", hex(&bytes));
let (base_url, requests, server) = test_server(
format!(r#"{{"tag_name":"v{version}"}}"#).into_bytes(),
bytes,
checksum.into_bytes(),
);
let (base_url, requests, server) =
test_server(version.as_bytes().to_vec(), bytes, checksum.into_bytes());
let resolver = OpyProviderResolver::new(&root)
.with_target(target)
.with_release_urls(format!("{base_url}/latest"), &base_url);
.with_release_urls(
format!("{base_url}/opy-rs/latest/version"),
format!("{base_url}/opy-rs/releases"),
);
let resolved = resolver.resolve(None).unwrap();
server.join().unwrap();
assert_eq!(requests.load(Ordering::Relaxed), 3);
Expand Down Expand Up @@ -774,14 +762,14 @@ mod tests {
let version = "1.0.0";
let bytes = archive(version, &target, b"windows-provider");
let checksum = format!("{} opy-provider-{version}-{target}.tar.gz\n", hex(&bytes));
let (base_url, requests, server) = test_server(
format!(r#"{{"tag_name":"v{version}"}}"#).into_bytes(),
bytes,
checksum.into_bytes(),
);
let (base_url, requests, server) =
test_server(version.as_bytes().to_vec(), bytes, checksum.into_bytes());
let resolver = OpyProviderResolver::new(&root)
.with_target(&target)
.with_release_urls(format!("{base_url}/latest"), &base_url);
.with_release_urls(
format!("{base_url}/opy-rs/latest/version"),
format!("{base_url}/opy-rs/releases"),
);
let updated = resolver.update(None).unwrap();
server.join().unwrap();
assert_eq!(requests.load(Ordering::Relaxed), 3);
Expand All @@ -791,7 +779,10 @@ mod tests {
b"windows-provider"
);
let resolved = resolver
.with_release_urls("http://127.0.0.1:1/latest", "http://127.0.0.1:1")
.with_release_urls(
"http://127.0.0.1:1/opy-rs/latest/version",
"http://127.0.0.1:1/opy-rs/releases",
)
.resolve(None)
.unwrap();
assert_eq!(resolved, updated);
Expand All @@ -809,7 +800,7 @@ mod tests {
}

fn test_server(
api: Vec<u8>,
latest_version: Vec<u8>,
archive: Vec<u8>,
checksum: Vec<u8>,
) -> (String, Arc<AtomicUsize>, thread::JoinHandle<()>) {
Expand All @@ -820,14 +811,14 @@ mod tests {
let server = thread::spawn(move || {
for _ in 0..3 {
let (mut stream, _) = listener.accept().unwrap();
respond(&mut stream, &api, &archive, &checksum);
respond(&mut stream, &latest_version, &archive, &checksum);
seen.fetch_add(1, Ordering::Relaxed);
}
});
(format!("http://{address}"), requests, server)
}

fn respond(stream: &mut TcpStream, api: &[u8], archive: &[u8], checksum: &[u8]) {
fn respond(stream: &mut TcpStream, latest_version: &[u8], archive: &[u8], checksum: &[u8]) {
let mut request = Vec::new();
let mut buffer = [0; 1024];
while !request.windows(4).any(|window| window == b"\r\n\r\n") {
Expand All @@ -839,16 +830,18 @@ mod tests {
}
let request = String::from_utf8_lossy(&request);
let path = request.split_whitespace().nth(1).unwrap_or_default();
let body = if path.ends_with("/latest") {
api
} else if path.ends_with(".sha256") {
checksum
let (status, body): (&str, &[u8]) = if path == "/opy-rs/latest/version" {
("200 OK", latest_version)
} else if path.starts_with("/opy-rs/releases/") && path.ends_with(".sha256") {
("200 OK", checksum)
} else if path.starts_with("/opy-rs/releases/") && path.ends_with(".tar.gz") {
("200 OK", archive)
} else {
archive
("404 Not Found", b"not found")
};
write!(
stream,
"HTTP/1.1 200 OK\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
"HTTP/1.1 {status}\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
body.len()
)
.unwrap();
Expand Down
Loading