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};
|
2023-05-16 12:15:01 -04:00
|
|
|
use tracing::{debug, error, instrument, trace, warn};
|
2022-11-29 02:21:44 -05:00
|
|
|
|
|
|
|
/// Abstraction over an adoptium API instance
|
2023-05-16 12:15:01 -04:00
|
|
|
#[derive(custom_debug::Debug)]
|
2022-11-29 02:21:44 -05:00
|
|
|
pub struct AdoptiumAPI {
|
|
|
|
/// Base URL
|
|
|
|
base_url: String,
|
|
|
|
/// Client
|
2023-05-16 12:15:01 -04:00
|
|
|
#[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
|
2023-05-16 12:15:01 -04:00
|
|
|
#[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
|
2023-05-16 12:15:01 -04:00
|
|
|
#[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" };
|
2023-05-16 12:15:01 -04:00
|
|
|
debug!(?release_type);
|
2022-11-29 02:21:44 -05:00
|
|
|
let arch = arch.as_ref();
|
|
|
|
let url = format!(
|
2023-05-16 12:15:01 -04:00
|
|
|
"{}/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
|
|
|
|
);
|
2023-05-16 12:15:01 -04:00
|
|
|
trace!(?url);
|
2022-11-29 02:21:44 -05:00
|
|
|
let mut response = self
|
|
|
|
.client
|
2023-05-16 12:15:01 -04:00
|
|
|
.get_async(&url)
|
2022-11-29 02:21:44 -05:00
|
|
|
.await
|
2023-05-15 17:10:18 -04:00
|
|
|
.context("Failed to request release")?;
|
2023-05-16 12:15:01 -04:00
|
|
|
debug!(?response);
|
2023-05-15 17:10:18 -04:00
|
|
|
// If we get a 301, respond to it
|
2023-05-16 12:15:01 -04:00
|
|
|
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");
|
|
|
|
}
|
|
|
|
_ => (),
|
2023-05-15 17:10:18 -04:00
|
|
|
}
|
|
|
|
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,
|
|
|
|
);
|
|
|
|
}
|
2023-05-16 12:15:01 -04:00
|
|
|
let latest: OutputRelease = match self
|
2022-11-29 02:21:44 -05:00
|
|
|
.latest(input_versions.most_recent_feature_version, arch, true)
|
|
|
|
.await
|
2023-05-16 12:15:01 -04:00
|
|
|
{
|
|
|
|
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,
|
2023-05-16 12:15:01 -04:00
|
|
|
false,
|
2022-11-29 02:21:44 -05:00
|
|
|
)
|
|
|
|
.await
|
2023-05-15 17:10:18 -04:00
|
|
|
.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,
|
2023-05-16 12:15:01 -04:00
|
|
|
false,
|
2022-11-29 02:21:44 -05:00
|
|
|
)
|
|
|
|
.await
|
2023-05-15 17:10:18 -04:00
|
|
|
.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,
|
|
|
|
}
|