java/updater/src/api.rs

283 lines
8.8 KiB
Rust
Raw Normal View History

2022-11-29 02:21:44 -05:00
use std::collections::BTreeMap;
use async_std::io::ReadExt;
use color_eyre::{
eyre::{Context, ContextCompat},
Result,
};
use isahc::HttpClient;
use serde::{Deserialize, Serialize};
use tracing::{debug, error, instrument, trace, warn};
2022-11-29 02:21:44 -05:00
/// Abstraction over an adoptium API instance
#[derive(custom_debug::Debug)]
2022-11-29 02:21:44 -05:00
pub struct AdoptiumAPI {
/// Base URL
base_url: String,
/// Client
#[debug(skip)]
2022-11-29 02:21:44 -05:00
client: HttpClient,
/// Jvm_Impl string
jvm_impl: String,
}
impl AdoptiumAPI {
/// Create a new adoptium api pointing at adoptium
pub fn adoptium() -> Result<Self> {
Ok(AdoptiumAPI {
base_url: "https://api.adoptium.net".to_string(),
client: HttpClient::new().context("failed to open http client")?,
jvm_impl: "hotspot".to_string(),
})
}
/// Create a new api pointing at semeru
pub fn semeru() -> Result<Self> {
Ok(AdoptiumAPI {
base_url: "https://api.adoptopenjdk.net".to_string(),
client: HttpClient::new().context("failed to open http client")?,
jvm_impl: "openj9".to_string(),
})
}
/// Get the availble releases
#[instrument]
2022-11-29 02:21:44 -05:00
pub async fn available_releases(&self) -> Result<AvailableReleases> {
let response = self
.client
.get_async(format!("{}/v3/info/available_releases", self.base_url))
.await
.context("Failed to request releases")?;
let mut body = response.into_body();
let mut contents = String::new();
body.read_to_string(&mut contents)
.await
.context("Failed to convert body to string")?;
let output = serde_json::from_str(&contents).context("Failed to parse json")?;
Ok(output)
}
/// Return the most recent version for the specified release and architecture
pub async fn release(
&self,
version: u32,
arch: impl AsRef<str>,
pre_release: bool,
) -> Result<Version> {
let release_type = if pre_release { "ea" } else { "ga" };
let arch = arch.as_ref();
let version = format!("[{version}, {})", version + 1);
let version = urlencoding::encode(&version);
let url = format!(
"{}/v3/info/release_versions?architecture={arch}&heap_size=normal&image_type=jdk&os_type=linux&project=jdk&release_type={release_type}&sort_method=DATE&sort_order=DESC&jvm_impl={}&version={version}",
self.base_url,
self.jvm_impl,
);
let mut response = self
.client
.get_async(url)
.await
.context("Failed to request release")?
.into_body();
let mut body = String::new();
response
.read_to_string(&mut body)
.await
.context("Failed to convert response to string")?;
let output: Versions = serde_json::from_str(&body).context("Failed to parse json")?;
let output = output.versions.get(0).context("No Versions")?;
Ok(output.clone())
}
/// Return latest release
#[instrument(skip(arch), fields(arch = arch.as_ref()))]
2022-11-29 02:21:44 -05:00
pub async fn latest(
&self,
version: u32,
arch: impl AsRef<str>,
pre_release: bool,
) -> Result<Release> {
let release_type = if pre_release { "ea" } else { "ga" };
debug!(?release_type);
2022-11-29 02:21:44 -05:00
let arch = arch.as_ref();
let url = format!(
"{}/v3/assets/feature_releases/{version}/{release_type}?architecture={arch}&heap_size=normal&image_type=jdk&os=linux&page=0&page_size=10&project=jdk&sort_method=DATE&sort_order=DESC&jvm_impl={}",
2022-11-29 02:21:44 -05:00
self.base_url,
self.jvm_impl
);
trace!(?url);
2022-11-29 02:21:44 -05:00
let mut response = self
.client
.get_async(&url)
2022-11-29 02:21:44 -05:00
.await
.context("Failed to request release")?;
debug!(?response);
// If we get a 301, respond to it
match response.status().as_u16() {
301 => {
let location = response
.headers()
.get("location")
.context("Failed to get redirect location")?
.to_str()
.context("Failed to parse redirect location")?;
warn!(?location, ?url, "Redirecting");
response = self
.client
.get_async(location)
.await
.context("Failed to request release")?;
debug!(?response, "New response");
}
404 => {
error!(?url, "Location not found");
}
_ => (),
}
let mut response = response.into_body();
2022-11-29 02:21:44 -05:00
let mut body = String::new();
response
.read_to_string(&mut body)
.await
.context("Failed to convert response to string")?;
let releases: Vec<Release> = serde_json::from_str(&body).context("Failed to parse json")?;
let release = releases.get(0).context("No releases")?;
Ok(release.clone())
}
/// Get all versions
pub async fn get_all(&self, arch: impl AsRef<str>) -> Result<OutputReleases> {
let input_versions = self
.available_releases()
.await
.context("Failed to get releases")?;
let arch = arch.as_ref();
let mut versions: BTreeMap<String, OutputRelease> = BTreeMap::new();
for release in &input_versions.available_releases {
let output_release: OutputRelease = self
.latest(*release, arch, false)
.await
.context("Failed to get version")?
.into();
versions.insert(
format!("jdk{}", output_release.major_version),
output_release,
);
}
let latest: OutputRelease = match self
2022-11-29 02:21:44 -05:00
.latest(input_versions.most_recent_feature_version, arch, true)
.await
{
Ok(x) => x.into(),
Err(_) => self
.latest(input_versions.most_recent_feature_release, arch, false)
.await
.context("Failed to get latest version")?
.into(),
};
2022-11-29 02:21:44 -05:00
let stable: OutputRelease = self
.latest(
input_versions.available_releases[input_versions.available_releases.len() - 1],
arch,
false,
2022-11-29 02:21:44 -05:00
)
.await
.context("Failed to get version - stable")?
2022-11-29 02:21:44 -05:00
.into();
let lts: OutputRelease = self
.latest(
input_versions.available_lts_releases
[input_versions.available_lts_releases.len() - 1],
arch,
false,
2022-11-29 02:21:44 -05:00
)
.await
.context("Failed to get version - lts")?
2022-11-29 02:21:44 -05:00
.into();
Ok(OutputReleases {
versions,
latest,
stable,
lts,
})
}
}
/// Output for an arch
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct OutputReleases {
versions: BTreeMap<String, OutputRelease>,
latest: OutputRelease,
stable: OutputRelease,
lts: OutputRelease,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct OutputRelease {
link: String,
major_version: u32,
sha256: String,
java_version: String,
}
impl From<Release> for OutputRelease {
fn from(release: Release) -> Self {
OutputRelease {
link: release.binaries[0].package.link.clone(),
major_version: release.version_data.major,
sha256: release.binaries[0].package.checksum.clone(),
java_version: release.version_data.openjdk_version,
}
}
}
/// `/v3/info/available_releases`
#[derive(Debug, Serialize, Deserialize)]
pub struct AvailableReleases {
pub available_releases: Vec<u32>,
pub available_lts_releases: Vec<u32>,
pub most_recent_lts: u32,
pub most_recent_feature_release: u32,
pub most_recent_feature_version: u32,
pub tip_version: u32,
}
/// `/v3/info/release_verions`
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Version {
major: u32,
minor: u32,
security: u32,
patch: Option<u32>,
pre: Option<String>,
adopt_build_number: Option<u64>,
semver: String,
openjdk_version: String,
build: Option<u64>,
optional: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Versions {
versions: Vec<Version>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Release {
version_data: Version,
binaries: Vec<Binary>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Binary {
architecture: String,
package: Package,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Package {
checksum: String,
link: String,
name: String,
}