Parse "registry.example.com/repo/name" into ("registry.example.com", "repo/name"). For Docker Hub short names like "dstacktee/guest-image" (no dots in the first component), automatically expands to "registry-1.docker.io/dstacktee/guest-image".
(image_ref: &str)
| 367 | /// For Docker Hub short names like "dstacktee/guest-image" (no dots in the |
| 368 | /// first component), automatically expands to "registry-1.docker.io/dstacktee/guest-image". |
| 369 | fn parse_image_ref(image_ref: &str) -> Result<(String, String)> { |
| 370 | let trimmed = image_ref |
| 371 | .trim_start_matches("https://") |
| 372 | .trim_start_matches("http://"); |
| 373 | |
| 374 | let first_slash = trimmed |
| 375 | .find('/') |
| 376 | .context("invalid image reference: no repository path")?; |
| 377 | |
| 378 | let first_component = &trimmed[..first_slash]; |
| 379 | let repo = &trimmed[first_slash + 1..]; |
| 380 | |
| 381 | if repo.is_empty() { |
| 382 | bail!("invalid image reference: empty repository"); |
| 383 | } |
| 384 | |
| 385 | // Docker Hub short names don't contain dots or colons |
| 386 | let registry = if first_component.contains('.') || first_component.contains(':') { |
| 387 | first_component.to_string() |
| 388 | } else { |
| 389 | // Docker Hub: "user/repo" → "registry-1.docker.io" |
| 390 | // and the repo needs "library/" prefix for official images |
| 391 | return Ok(( |
| 392 | "registry-1.docker.io".to_string(), |
| 393 | format!("{first_component}/{repo}"), |
| 394 | )); |
| 395 | }; |
| 396 | |
| 397 | Ok((registry, repo.to_string())) |
| 398 | } |
| 399 | |
| 400 | // ─── OCI types ────────────────────────────────────────────────────────────── |
| 401 |