use crate::{Error, Result};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ImageRef {
pub registry: String,
pub repository: String,
pub reference: String,
}
impl ImageRef {
pub fn parse(image: &str) -> Result<Self> {
let image = image.trim();
if image.is_empty() {
return Err(Error::BadReference(image.to_string()));
}
let (name, digest) = match image.split_once('@') {
Some((n, d)) => (n, Some(d)),
None => (image, None),
};
let (registry, rest) = match name.split_once('/') {
Some((first, rest))
if first.contains('.') || first.contains(':') || first == "localhost" =>
{
(first.to_string(), rest.to_string())
}
_ => ("docker.io".to_string(), name.to_string()),
};
let (repository, tag) = match rest.rfind(':') {
Some(i) if !rest[i..].contains('/') => {
(rest[..i].to_string(), rest[i + 1..].to_string())
}
_ => (rest, "latest".to_string()),
};
let repository = if registry == "docker.io" && !repository.contains('/') {
format!("library/{repository}")
} else {
repository
};
if repository.is_empty() || repository.ends_with('/') {
return Err(Error::BadReference(image.to_string()));
}
Ok(Self {
registry,
repository,
reference: digest.map(str::to_string).unwrap_or(tag),
})
}
pub fn canonical(&self) -> String {
if self.reference.contains(':') {
format!("{}/{}@{}", self.registry, self.repository, self.reference)
} else {
format!("{}/{}:{}", self.registry, self.repository, self.reference)
}
}
pub fn api_base(&self) -> String {
let host = if self.registry == "docker.io" {
"registry-1.docker.io"
} else {
&self.registry
};
let scheme = if host.starts_with("localhost") || host.starts_with("127.0.0.1") {
"http"
} else {
"https"
};
format!("{scheme}://{host}/v2/{}", self.repository)
}
}