1
0
mirror of https://github.com/containers/bootc.git synced 2026-02-06 09:45:32 +01:00

feat(switch): support tag + digest image reference

Closes #1165

Performs tag-stripping when the image reference contains both
a tag and digest. This allows Skopeo to pull the image
successfully, while still displaying both the tag + digest inside
bootc status.

Signed-off-by: Robert Sturla <robertsturla@outlook.com>
This commit is contained in:
Robert Sturla
2025-05-09 01:17:17 +01:00
parent 93b22f4dbc
commit 10b66fbc6c
2 changed files with 81 additions and 3 deletions

View File

@@ -340,7 +340,9 @@ pub(crate) async fn prepare_for_pull(
imgref: &ImageReference,
target_imgref: Option<&OstreeImageReference>,
) -> Result<PreparedPullResult> {
let ostree_imgref = &OstreeImageReference::from(imgref.clone());
let imgref_canonicalized = imgref.clone().canonicalize()?;
tracing::debug!("Canonicalized image reference: {imgref_canonicalized:#}");
let ostree_imgref = &OstreeImageReference::from(imgref_canonicalized);
let mut imp = new_importer(repo, ostree_imgref).await?;
if let Some(target) = target_imgref {
imp.set_target(target);
@@ -420,7 +422,9 @@ pub(crate) async fn pull_from_prepared(
})
.await;
let import = import?;
let ostree_imgref = &OstreeImageReference::from(imgref.clone());
let imgref_canonicalized = imgref.clone().canonicalize()?;
tracing::debug!("Canonicalized image reference: {imgref_canonicalized:#}");
let ostree_imgref = &OstreeImageReference::from(imgref_canonicalized);
let wrote_imgref = target_imgref.as_ref().unwrap_or(&ostree_imgref);
if let Some(msg) =

View File

@@ -2,8 +2,8 @@
use std::fmt::Display;
use ostree_ext::container::OstreeImageReference;
use ostree_ext::oci_spec::image::Digest;
use ostree_ext::{container::OstreeImageReference, oci_spec};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -89,6 +89,25 @@ pub struct ImageReference {
pub signature: Option<ImageSignature>,
}
impl ImageReference {
/// Returns a canonicalized version of this image reference, preferring the digest over the tag if both are present.
pub fn canonicalize(self) -> Result<Self, anyhow::Error> {
let reference: oci_spec::distribution::Reference = self.image.parse()?;
if reference.digest().is_some() && reference.tag().is_some() {
let registry = reference.registry();
let repository = reference.repository();
let digest = reference.digest().expect("digest is present");
return Ok(ImageReference {
image: format!("{registry}/{repository}@{digest}"),
..self
});
}
Ok(self)
}
}
/// The status of the booted image
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "camelCase")]
@@ -253,6 +272,61 @@ mod tests {
use super::*;
#[test]
fn test_image_reference_canonicalize() {
let sample_digest =
"sha256:5db6d8b5f34d3cbdaa1e82ed0152a5ac980076d19317d4269db149cbde057bb2";
let test_cases = [
// When both a tag and digest are present, the digest should be used
(
format!("quay.io/example/someimage:latest@{}", sample_digest),
format!("quay.io/example/someimage@{}", sample_digest),
),
// When only a digest is present, it should be used
(
format!("quay.io/example/someimage@{}", sample_digest),
format!("quay.io/example/someimage@{}", sample_digest),
),
// When only a tag is present, it should be preserved
(
"quay.io/example/someimage:latest".to_string(),
"quay.io/example/someimage:latest".to_string(),
),
// When no tag or digest is present, preserve the original image name
(
"quay.io/example/someimage".to_string(),
"quay.io/example/someimage".to_string(),
),
// When used with a local image (i.e. from containers-storage), the functionality should
// be the same as previous cases
(
"localhost/someimage:latest".to_string(),
"localhost/someimage:latest".to_string(),
),
(
format!("localhost/someimage:latest@{sample_digest}"),
format!("localhost/someimage@{sample_digest}"),
),
];
for (initial, expected) in test_cases {
let imgref = ImageReference {
image: initial.to_string(),
transport: "registry".to_string(),
signature: None,
};
let canonicalized = imgref.canonicalize();
if let Err(e) = canonicalized {
panic!("Failed to canonicalize {initial}: {e}");
}
let canonicalized = canonicalized.unwrap();
assert_eq!(canonicalized.image, expected);
assert_eq!(canonicalized.transport, "registry");
assert_eq!(canonicalized.signature, None);
}
}
#[test]
fn test_parse_spec_v1_null() {
const SPEC_FIXTURE: &str = include_str!("fixtures/spec-v1-null.json");