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

--transport docker-daemon support

So we can do things like:

sudo bootc switch --transport docker-daemon localhost/bootc:latest

Signed-off-by: Eric Curtin <eric.curtin@docker.com>
This commit is contained in:
Eric Curtin
2025-11-18 09:38:10 +00:00
committed by Colin Walters
parent a998bfc3f1
commit 086fa3bbd3
5 changed files with 46 additions and 4 deletions

View File

@@ -56,12 +56,15 @@ pub(crate) async fn initialize_composefs_repository(
/// Ex
/// docker://quay.io/some-image
/// containers-storage:some-image
/// docker-daemon:some-image-id
pub(crate) fn get_imgref(transport: &str, image: &str) -> String {
let img = image.strip_prefix(":").unwrap_or(&image);
let transport = transport.strip_suffix(":").unwrap_or(&transport);
if transport == "registry" {
format!("docker://{img}")
} else if transport == "docker-daemon" {
format!("docker-daemon:{img}")
} else {
format!("{transport}:{img}")
}
@@ -138,4 +141,12 @@ mod tests {
format!("docker://{IMAGE_NAME}")
);
}
#[test]
fn test_get_imgref_docker_daemon_transport() {
assert_eq!(
get_imgref("docker-daemon", IMAGE_NAME),
format!("docker-daemon:{IMAGE_NAME}")
);
}
}

View File

@@ -123,7 +123,7 @@ pub(crate) struct SwitchOpts {
#[clap(long = "soft-reboot")]
pub(crate) soft_reboot: Option<SoftRebootMode>,
/// The transport; e.g. oci, oci-archive, containers-storage. Defaults to `registry`.
/// The transport; e.g. registry, oci, oci-archive, docker-daemon, containers-storage. Defaults to `registry`.
#[clap(long, default_value = "registry")]
pub(crate) transport: String,

View File

@@ -66,6 +66,8 @@ pub enum Transport {
ContainerStorage,
/// Local directory (`dir:`)
Dir,
/// Local Docker daemon (`docker-daemon:`)
DockerDaemon,
}
/// Combination of a remote image reference and transport.
@@ -114,6 +116,7 @@ impl TryFrom<&str> for Transport {
Self::DOCKER_ARCHIVE_STR => Self::DockerArchive,
Self::CONTAINERS_STORAGE_STR => Self::ContainerStorage,
Self::LOCAL_DIRECTORY_STR => Self::Dir,
Self::DOCKER_DAEMON_STR => Self::DockerDaemon,
o => return Err(anyhow!("Unknown transport '{}'", o)),
})
}
@@ -126,6 +129,7 @@ impl Transport {
const CONTAINERS_STORAGE_STR: &'static str = "containers-storage";
const LOCAL_DIRECTORY_STR: &'static str = "dir";
const REGISTRY_STR: &'static str = "registry";
const DOCKER_DAEMON_STR: &'static str = "docker-daemon";
/// Retrieve an identifier that can then be re-parsed from [`Transport::try_from::<&str>`].
pub fn serializable_name(&self) -> &'static str {
@@ -136,6 +140,7 @@ impl Transport {
Transport::DockerArchive => Self::DOCKER_ARCHIVE_STR,
Transport::ContainerStorage => Self::CONTAINERS_STORAGE_STR,
Transport::Dir => Self::LOCAL_DIRECTORY_STR,
Transport::DockerDaemon => Self::DOCKER_DAEMON_STR,
}
}
}
@@ -258,6 +263,7 @@ impl std::fmt::Display for Transport {
Self::OciDir => "oci:",
Self::ContainerStorage => "containers-storage:",
Self::Dir => "dir:",
Self::DockerDaemon => "docker-daemon:",
};
f.write_str(s)
}

View File

@@ -205,9 +205,13 @@ pub(crate) async fn fetch_layer<'a>(
let (blob, driver, size);
let media_type: oci_image::MediaType;
match transport_src {
Transport::ContainerStorage => {
let layer_info = layer_info
.ok_or_else(|| anyhow!("skopeo too old to pull from containers-storage"))?;
// Both containers-storage and docker-daemon store layers uncompressed in their
// local storage, even though the manifest may indicate they are compressed.
// We need to use the actual media type from layer_info to avoid decompression errors.
Transport::ContainerStorage | Transport::DockerDaemon => {
let layer_info = layer_info.ok_or_else(|| {
anyhow!("skopeo too old to pull from containers-storage or docker-daemon")
})?;
let n_layers = layer_info.len();
let layer_blob = layer_info.get(layer_index).ok_or_else(|| {
anyhow!("blobid position {layer_index} exceeds diffid count {n_layers}")

View File

@@ -22,6 +22,11 @@ use crate::oci_spec::image as oci_image;
/// TODO: change the skopeo code to shield us from this correctly
const DOCKER_TYPE_LAYER_TAR: &str = "application/vnd.docker.image.rootfs.diff.tar";
/// The Docker MIME type for gzipped layers when stored in docker-daemon.
/// Even though this indicates gzip compression, docker-daemon actually stores
/// the layers uncompressed, so we need to treat this as uncompressed.
const DOCKER_TYPE_LAYER_TAR_GZIP: &str = "application/vnd.docker.image.rootfs.diff.tar.gzip";
/// Extends the `Read` trait with another method to get mutable access to the inner reader
trait ReadWithGetInnerMut: Read + Send + 'static {
fn get_inner_mut(&mut self) -> &mut dyn Read;
@@ -125,6 +130,9 @@ impl Decompressor {
oci_image::MediaType::Other(t) if t.as_str() == DOCKER_TYPE_LAYER_TAR => {
Box::new(TransparentDecompressor(src))
}
oci_image::MediaType::Other(t) if t.as_str() == DOCKER_TYPE_LAYER_TAR_GZIP => {
Box::new(TransparentDecompressor(src))
}
o => anyhow::bail!("Unhandled layer type: {}", o),
};
Ok(Self {
@@ -228,4 +236,17 @@ mod tests {
assert_eq!(e.to_string(), "Unknown frame descriptor".to_string());
drop(d)
}
#[test]
fn test_docker_tar_gzip_media_type_uses_transparent_decompressor() {
// Test that the docker-daemon gzip media type is treated as uncompressed
let data = b"test data";
let media_type = oci_image::MediaType::Other(DOCKER_TYPE_LAYER_TAR_GZIP.to_string());
let mut d = Decompressor::new(&media_type, &data[..]).unwrap();
let mut buf = [0u8; 32];
let n = d.read(&mut buf).unwrap();
assert_eq!(n, data.len());
assert_eq!(&buf[..n], data);
drop(d)
}
}