mirror of
https://github.com/taiki-e/install-action.git
synced 2026-05-10 22:50:32 +00:00
codegen: Check that the digest matches GitHub Releases' one
https://github.blog/changelog/2025-06-03-releases-now-expose-digests-for-release-assets/
This commit is contained in:
@@ -50,18 +50,18 @@ fn main() -> Result<()> {
|
|||||||
.strip_prefix("https://github.com/")
|
.strip_prefix("https://github.com/")
|
||||||
.context("repository must start with https://github.com/")?;
|
.context("repository must start with https://github.com/")?;
|
||||||
|
|
||||||
eprintln!("downloading metadata from https://api.github.com/repos/{repo}");
|
eprintln!("downloading metadata from {GITHUB_API_START}repos/{repo}");
|
||||||
let repo_info: github::RepoMetadata =
|
let repo_info: github::RepoMetadata =
|
||||||
download(&format!("https://api.github.com/repos/{repo}"))?.into_json()?;
|
download(&format!("{GITHUB_API_START}repos/{repo}"))?.into_json()?;
|
||||||
|
|
||||||
eprintln!("downloading releases from https://api.github.com/repos/{repo}/releases");
|
eprintln!("downloading releases from {GITHUB_API_START}repos/{repo}/releases");
|
||||||
let mut releases: github::Releases = vec![];
|
let mut releases: github::Releases = vec![];
|
||||||
// GitHub API returns up to 100 results at a time. If the number of releases
|
// GitHub API returns up to 100 results at a time. If the number of releases
|
||||||
// is greater than 100, multiple fetches are needed.
|
// is greater than 100, multiple fetches are needed.
|
||||||
for page in 1.. {
|
for page in 1.. {
|
||||||
let per_page = 100;
|
let per_page = 100;
|
||||||
let mut r: github::Releases = download(&format!(
|
let mut r: github::Releases = download(&format!(
|
||||||
"https://api.github.com/repos/{repo}/releases?per_page={per_page}&page={page}"
|
"{GITHUB_API_START}repos/{repo}/releases?per_page={per_page}&page={page}"
|
||||||
))?
|
))?
|
||||||
.into_json()?;
|
.into_json()?;
|
||||||
// If version_req is latest, it is usually sufficient to look at the latest 100 releases.
|
// If version_req is latest, it is usually sufficient to look at the latest 100 releases.
|
||||||
@@ -295,7 +295,7 @@ fn main() -> Result<()> {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>>>()?;
|
.collect::<Result<Vec<_>>>()?;
|
||||||
let (url, asset_name) = match asset_names.iter().find_map(|asset_name| {
|
let (url, digest, asset_name) = match asset_names.iter().find_map(|asset_name| {
|
||||||
release
|
release
|
||||||
.assets
|
.assets
|
||||||
.iter()
|
.iter()
|
||||||
@@ -303,7 +303,7 @@ fn main() -> Result<()> {
|
|||||||
.map(|asset| (asset, asset_name))
|
.map(|asset| (asset, asset_name))
|
||||||
}) {
|
}) {
|
||||||
Some((asset, asset_name)) => {
|
Some((asset, asset_name)) => {
|
||||||
(asset.browser_download_url.clone(), asset_name.clone())
|
(asset.browser_download_url.clone(), &asset.digest, asset_name.clone())
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
eprintln!("no asset '{asset_names:?}' for host platform '{platform:?}'");
|
eprintln!("no asset '{asset_names:?}' for host platform '{platform:?}'");
|
||||||
@@ -345,6 +345,13 @@ fn main() -> Result<()> {
|
|||||||
eprintln!("getting sha256 hash for {url}");
|
eprintln!("getting sha256 hash for {url}");
|
||||||
let hash = Sha256::digest(&buf);
|
let hash = Sha256::digest(&buf);
|
||||||
let hash = format!("{hash:x}");
|
let hash = format!("{hash:x}");
|
||||||
|
if let Some(digest) = digest {
|
||||||
|
if hash != digest.strip_prefix("sha256:").unwrap() {
|
||||||
|
bail!(
|
||||||
|
"digest mismatch between GitHub release page and actually downloaded file"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
eprintln!("{hash} *{asset_name}");
|
eprintln!("{hash} *{asset_name}");
|
||||||
let bin_url = &url;
|
let bin_url = &url;
|
||||||
|
|
||||||
@@ -687,11 +694,13 @@ struct GitHubTokens {
|
|||||||
// https://github.com/*/*/releases/download/
|
// https://github.com/*/*/releases/download/
|
||||||
other: RwLock<Option<String>>,
|
other: RwLock<Option<String>>,
|
||||||
}
|
}
|
||||||
|
const GITHUB_API_START: &str = "https://api.github.com/";
|
||||||
|
const GITHUB_RAW_START: &str = "https://raw.githubusercontent.com/";
|
||||||
impl GitHubTokens {
|
impl GitHubTokens {
|
||||||
fn get(&self, url: &str) -> Option<String> {
|
fn get(&self, url: &str) -> Option<String> {
|
||||||
if url.starts_with("https://raw.githubusercontent.com/") {
|
if url.starts_with(GITHUB_RAW_START) {
|
||||||
self.raw.read().unwrap().clone()
|
self.raw.read().unwrap().clone()
|
||||||
} else if url.starts_with("https://api.github.com/") {
|
} else if url.starts_with(GITHUB_API_START) {
|
||||||
self.api.read().unwrap().clone()
|
self.api.read().unwrap().clone()
|
||||||
} else if url.starts_with("https://github.com/") {
|
} else if url.starts_with("https://github.com/") {
|
||||||
self.other.read().unwrap().clone()
|
self.other.read().unwrap().clone()
|
||||||
@@ -700,9 +709,9 @@ impl GitHubTokens {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn clear(&self, url: &str) {
|
fn clear(&self, url: &str) {
|
||||||
if url.starts_with("https://raw.githubusercontent.com/") {
|
if url.starts_with(GITHUB_RAW_START) {
|
||||||
*self.raw.write().unwrap() = None;
|
*self.raw.write().unwrap() = None;
|
||||||
} else if url.starts_with("https://api.github.com/") {
|
} else if url.starts_with(GITHUB_API_START) {
|
||||||
*self.api.write().unwrap() = None;
|
*self.api.write().unwrap() = None;
|
||||||
} else if url.starts_with("https://github.com/") {
|
} else if url.starts_with("https://github.com/") {
|
||||||
*self.other.write().unwrap() = None;
|
*self.other.write().unwrap() = None;
|
||||||
@@ -723,6 +732,7 @@ fn download(url: &str) -> Result<ureq::Response> {
|
|||||||
let mut retry = 0;
|
let mut retry = 0;
|
||||||
let mut retry_time = 0;
|
let mut retry_time = 0;
|
||||||
let mut max_retry = 6;
|
let mut max_retry = 6;
|
||||||
|
let is_github_api = url.starts_with(GITHUB_API_START);
|
||||||
if token.is_none() {
|
if token.is_none() {
|
||||||
max_retry /= 2;
|
max_retry /= 2;
|
||||||
}
|
}
|
||||||
@@ -732,6 +742,11 @@ fn download(url: &str) -> Result<ureq::Response> {
|
|||||||
if let Some(token) = &token {
|
if let Some(token) = &token {
|
||||||
req = req.set("Authorization", &format!("Bearer {token}"));
|
req = req.set("Authorization", &format!("Bearer {token}"));
|
||||||
}
|
}
|
||||||
|
if is_github_api {
|
||||||
|
req = req
|
||||||
|
.set("Accept", "application/vnd.github+json")
|
||||||
|
.set("X-GitHub-Api-Version", "2022-11-28");
|
||||||
|
}
|
||||||
match req.call() {
|
match req.call() {
|
||||||
Ok(res) => return Ok(res),
|
Ok(res) => return Ok(res),
|
||||||
Err(e) => last_error = Some(e),
|
Err(e) => last_error = Some(e),
|
||||||
@@ -793,13 +808,13 @@ fn github_head(url: &str) -> Result<()> {
|
|||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn create_github_raw_link(repository: &str, branch: &str, filename: &str) -> String {
|
fn create_github_raw_link(repository: &str, branch: &str, filename: &str) -> String {
|
||||||
format!("https://raw.githubusercontent.com/{repository}/{branch}/{filename}")
|
format!("{GITHUB_RAW_START}{repository}/{branch}/{filename}")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create URLs for https://docs.github.com/en/rest/repos/contents
|
/// Create URLs for https://docs.github.com/en/rest/repos/contents
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn github_content_api_url(repository: &str, branch: &str, filename: &str) -> String {
|
fn github_content_api_url(repository: &str, branch: &str, filename: &str) -> String {
|
||||||
format!("https://api.github.com/repos/{repository}/contents/{filename}?ref={branch}")
|
format!("{GITHUB_API_START}repos/{repository}/contents/{filename}?ref={branch}")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@@ -958,6 +973,8 @@ mod github {
|
|||||||
pub(crate) struct ReleaseAsset {
|
pub(crate) struct ReleaseAsset {
|
||||||
pub(crate) name: String,
|
pub(crate) name: String,
|
||||||
// pub(crate) content_type: String,
|
// pub(crate) content_type: String,
|
||||||
|
// Note that this field is null if the release is old.
|
||||||
|
pub(crate) digest: Option<String>,
|
||||||
pub(crate) browser_download_url: String,
|
pub(crate) browser_download_url: String,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user