pub fn normalize_image_ref(image: &str) -> String {
let owned;
let image = if let Some(rest) = image.strip_prefix("index.docker.io/") {
owned = format!("docker.io/{rest}");
owned.as_str()
} else {
image
};
let (ref_no_digest, digest) = match image.split_once('@') {
Some((left, right)) => (left, Some(right)),
None => (image, None),
};
let (ref_no_tag, tag) = match ref_no_digest.rfind(':') {
Some(pos) if !ref_no_digest[pos..].contains('/') => {
(&ref_no_digest[..pos], Some(&ref_no_digest[pos + 1..]))
}
_ => (ref_no_digest, None),
};
let (registry, path) = registry_and_path(ref_no_tag);
let canonical_path = if registry == "docker.io" && !path.contains('/') {
format!("library/{path}")
} else {
path.to_string()
};
let suffix = match digest {
Some(d) => format!("@{d}"),
None => format!(":{}", tag.unwrap_or("latest")),
};
format!("{registry}/{canonical_path}{suffix}")
}
fn registry_and_path(image: &str) -> (&str, &str) {
if let Some(slash) = image.find('/') {
let prefix = &image[..slash];
if prefix.contains('.') || prefix.contains(':') {
return (prefix, &image[slash + 1..]);
}
}
("docker.io", image)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_normalize_image_ref() {
let cases: &[(&str, &str)] = &[
("alpine", "docker.io/library/alpine:latest"),
("alpine:3.20", "docker.io/library/alpine:3.20"),
("docker.io/alpine:3.20", "docker.io/library/alpine:3.20"),
(
"docker.io/library/alpine:3.20",
"docker.io/library/alpine:3.20",
),
(
"index.docker.io/library/alpine",
"docker.io/library/alpine:latest",
),
("library/alpine", "docker.io/library/alpine:latest"),
("myuser/myimage:v2", "docker.io/myuser/myimage:v2"),
("ghcr.io/owner/repo", "ghcr.io/owner/repo:latest"),
("ghcr.io/owner/repo:v1", "ghcr.io/owner/repo:v1"),
("localhost:5000/myimage:dev", "localhost:5000/myimage:dev"),
];
for (input, expected) in cases {
assert_eq!(
normalize_image_ref(input),
*expected,
"normalize_image_ref({input:?})"
);
}
}
#[test]
fn test_normalize_digest_refs() {
let digest = "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
assert_eq!(
normalize_image_ref(&format!("alpine@{digest}")),
format!("docker.io/library/alpine@{digest}"),
);
assert_eq!(
normalize_image_ref(&format!("alpine:3.20@{digest}")),
format!("docker.io/library/alpine@{digest}"),
);
}
}