m/cup
1
0
mirror of https://github.com/sergi0g/cup.git synced 2025-11-15 08:33:49 -05:00

Improve semver version handling (decrease false positives)
Some checks are pending
Deploy github pages / build (push) Waiting to run
Deploy github pages / deploy (push) Blocked by required conditions

This commit is contained in:
Sergio
2025-01-01 15:25:49 +02:00
parent 9c8e6ccdea
commit 0a8295fff4
4 changed files with 59 additions and 39 deletions

View File

@@ -149,7 +149,7 @@ pub async fn get_latest_tag(
tags.len()
);
let (new_tags, next) =
match get_extra_tags(&next_url.unwrap(), headers.clone(), base, client).await {
match get_extra_tags(&next_url.unwrap(), headers.clone(), base, &image.version_info.as_ref().unwrap().format_str, client).await {
Ok(t) => t,
Err(message) => {
return Image {
@@ -163,10 +163,6 @@ pub async fn get_latest_tag(
next_url = next;
}
let tag = tags.iter().max();
let current_tag = match &image.version_info {
Some(data) => data.current_tag.clone(),
_ => unreachable!(),
};
let time = timestamp() - start;
debug!(
config.debug,
@@ -179,8 +175,8 @@ pub async fn get_latest_tag(
get_latest_digest(
&Image {
version_info: Some(VersionInfo {
current_tag,
latest_remote_tag: Some(t.clone()),
..image.version_info.as_ref().unwrap().clone()
}),
time_ms: image.time_ms + time,
..image.clone()
@@ -193,8 +189,8 @@ pub async fn get_latest_tag(
} else {
Image {
version_info: Some(VersionInfo {
current_tag,
latest_remote_tag: Some(t.clone()),
..image.version_info.as_ref().unwrap().clone()
}),
time_ms: image.time_ms + time,
..image.clone()
@@ -209,6 +205,7 @@ pub async fn get_extra_tags(
url: &str,
headers: Vec<(&str, Option<&str>)>,
base: &Version,
format_str: &str,
client: &Client,
) -> Result<(Vec<Version>, Option<String>), String> {
let response = client.get(url, headers, false).await;
@@ -223,12 +220,13 @@ pub async fn get_extra_tags(
let result = response_json["tags"]
.members()
.filter_map(|tag| Version::from_tag(&tag.to_string()))
.filter(|tag| match (base.minor, tag.minor) {
.filter(|(tag, format_string)| match (base.minor, tag.minor) {
(Some(_), Some(_)) | (None, None) => {
matches!((base.patch, tag.patch), (Some(_), Some(_)) | (None, None))
matches!((base.patch, tag.patch), (Some(_), Some(_)) | (None, None)) && format_str == *format_string
}
_ => false,
})
.map(|(tag, _)| tag)
.dedup()
.collect();
Ok((result, next_url))

View File

@@ -23,6 +23,7 @@ pub struct DigestInfo {
pub struct VersionInfo {
pub current_tag: Version,
pub latest_remote_tag: Option<Version>,
pub format_str: String
}
/// Image struct that contains all information that may be needed by a function working with an image.
@@ -62,8 +63,9 @@ impl Image {
local_digests,
remote_digest: None,
}),
version_info: version_tag.map(|vtag| VersionInfo {
version_info: version_tag.map(|(vtag, format_str)| VersionInfo {
current_tag: vtag,
format_str,
latest_remote_tag: None,
}),
..Default::default()
@@ -78,13 +80,14 @@ impl Image {
let (registry, repository, tag) = split(reference);
let version_tag = Version::from_tag(&tag);
match version_tag {
Some(version) => Self {
Some((version, format_str)) => Self {
reference: reference.to_string(),
registry,
repository,
tag,
version_info: Some(VersionInfo {
current_tag: version,
format_str,
latest_remote_tag: None,
}),
..Default::default()

View File

@@ -14,8 +14,8 @@ pub struct Version {
}
impl Version {
/// Tries to parse the tag into semver-like parts.
pub fn from_tag(tag: &str) -> Option<Self> {
/// Tries to parse the tag into semver-like parts. Returns a Version object and a string usable in format! with {} in the positions matches were found
pub fn from_tag(tag: &str) -> Option<(Self, String)> {
/// Heavily modified version of the official semver regex based on common tagging schemes for container images. Sometimes it matches more than once, but we'll try to select the best match.
static VERSION_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(
@@ -43,19 +43,35 @@ impl Version {
}
match best_match {
Some(c) => {
let mut positions = Vec::new();
let major: u32 = match c.name("major") {
Some(major) => major.as_str().parse().unwrap(),
Some(major) => {
positions.push((major.start(), major.end()));
major.as_str().parse().unwrap()
}
None => return None,
};
let minor: Option<u32> =
c.name("minor").map(|minor| minor.as_str().parse().unwrap());
let patch: Option<u32> =
c.name("patch").map(|patch| patch.as_str().parse().unwrap());
Some(Version {
major,
minor,
patch,
})
let minor: Option<u32> = c.name("minor").map(|minor| {
positions.push((minor.start(), minor.end()));
minor.as_str().parse().unwrap()
});
let patch: Option<u32> = c.name("patch").map(|patch| {
positions.push((patch.start(), patch.end()));
patch.as_str().parse().unwrap()
});
let mut format_str = tag.to_string();
positions.reverse();
positions.iter().for_each(|(start, end)| {
format_str.replace_range(*start..*end, "{}");
});
Some((
Version {
major,
minor,
patch,
},
format_str,
))
}
None => None,
}
@@ -145,21 +161,21 @@ mod tests {
#[test]
#[rustfmt::skip]
fn version() {
assert_eq!(Version::from_tag("5.3.2" ), Some(Version { major: 5, minor: Some(3), patch: Some(2) }));
assert_eq!(Version::from_tag("14" ), Some(Version { major: 14, minor: None, patch: None }));
assert_eq!(Version::from_tag("v0.107.53" ), Some(Version { major: 0, minor: Some(107), patch: Some(53) }));
assert_eq!(Version::from_tag("12-alpine" ), Some(Version { major: 12, minor: None, patch: None }));
assert_eq!(Version::from_tag("0.9.5-nginx" ), Some(Version { major: 0, minor: Some(9), patch: Some(5) }));
assert_eq!(Version::from_tag("v27.0" ), Some(Version { major: 27, minor: Some(0), patch: None }));
assert_eq!(Version::from_tag("16.1" ), Some(Version { major: 16, minor: Some(1), patch: None }));
assert_eq!(Version::from_tag("version-1.5.6" ), Some(Version { major: 1, minor: Some(5), patch: Some(6) }));
assert_eq!(Version::from_tag("15.4-alpine" ), Some(Version { major: 15, minor: Some(4), patch: None }));
assert_eq!(Version::from_tag("pg14-v0.2.0" ), Some(Version { major: 0, minor: Some(2), patch: Some(0) }));
assert_eq!(Version::from_tag("18-jammy-full.s6-v0.88.0"), Some(Version { major: 0, minor: Some(88), patch: Some(0) }));
assert_eq!(Version::from_tag("fpm-2.1.0-prod" ), Some(Version { major: 2, minor: Some(1), patch: Some(0) }));
assert_eq!(Version::from_tag("7.3.3.50" ), Some(Version { major: 7, minor: Some(3), patch: Some(3) }));
assert_eq!(Version::from_tag("1.21.11-0" ), Some(Version { major: 1, minor: Some(21), patch: Some(11) }));
assert_eq!(Version::from_tag("4.1.2.1-full" ), Some(Version { major: 4, minor: Some(1), patch: Some(2) }));
assert_eq!(Version::from_tag("v4.0.3-ls215" ), Some(Version { major: 4, minor: Some(0), patch: Some(3) }));
assert_eq!(Version::from_tag("5.3.2" ), Some((Version { major: 5, minor: Some(3), patch: Some(2) }, String::from("{}.{}.{}" ))));
assert_eq!(Version::from_tag("14" ), Some((Version { major: 14, minor: None, patch: None }, String::from("{}" ))));
assert_eq!(Version::from_tag("v0.107.53" ), Some((Version { major: 0, minor: Some(107), patch: Some(53) }, String::from("v{}.{}.{}" ))));
assert_eq!(Version::from_tag("12-alpine" ), Some((Version { major: 12, minor: None, patch: None }, String::from("{}-alpine" ))));
assert_eq!(Version::from_tag("0.9.5-nginx" ), Some((Version { major: 0, minor: Some(9), patch: Some(5) }, String::from("{}.{}.{}-nginx" ))));
assert_eq!(Version::from_tag("v27.0" ), Some((Version { major: 27, minor: Some(0), patch: None }, String::from("v{}.{}" ))));
assert_eq!(Version::from_tag("16.1" ), Some((Version { major: 16, minor: Some(1), patch: None }, String::from("{}.{}" ))));
assert_eq!(Version::from_tag("version-1.5.6" ), Some((Version { major: 1, minor: Some(5), patch: Some(6) }, String::from("version-{}.{}.{}" ))));
assert_eq!(Version::from_tag("15.4-alpine" ), Some((Version { major: 15, minor: Some(4), patch: None }, String::from("{}.{}-alpine" ))));
assert_eq!(Version::from_tag("pg14-v0.2.0" ), Some((Version { major: 0, minor: Some(2), patch: Some(0) }, String::from("pg14-v{}.{}.{}" ))));
assert_eq!(Version::from_tag("18-jammy-full.s6-v0.88.0"), Some((Version { major: 0, minor: Some(88), patch: Some(0) }, String::from("18-jammy-full.s6-v{}.{}.{}"))));
assert_eq!(Version::from_tag("fpm-2.1.0-prod" ), Some((Version { major: 2, minor: Some(1), patch: Some(0) }, String::from("fpm-{}.{}.{}-prod" ))));
assert_eq!(Version::from_tag("7.3.3.50" ), Some((Version { major: 7, minor: Some(3), patch: Some(3) }, String::from("{}.{}.{}.50" ))));
assert_eq!(Version::from_tag("1.21.11-0" ), Some((Version { major: 1, minor: Some(21), patch: Some(11) }, String::from("{}.{}.{}-0" ))));
assert_eq!(Version::from_tag("4.1.2.1-full" ), Some((Version { major: 4, minor: Some(1), patch: Some(2) }, String::from("{}.{}.{}.1-full" ))));
assert_eq!(Version::from_tag("v4.0.3-ls215" ), Some((Version { major: 4, minor: Some(0), patch: Some(3) }, String::from("v{}.{}.{}-ls215" ))));
}
}

View File

@@ -126,6 +126,7 @@ mod tests {
minor: Some(42),
patch: Some(1000),
}),
format_str: String::new()
}),
..Default::default()
}
@@ -145,6 +146,7 @@ mod tests {
minor: Some(47),
patch: Some(2),
}),
format_str: String::new()
}),
..Default::default()
}
@@ -164,6 +166,7 @@ mod tests {
minor: Some(0),
patch: None,
}),
format_str: String::new()
}),
..Default::default()
}