bollard
bollard copied to clipboard
Copy entrypoint from image
Hello 👋 I was interested in using this library in a project to be able to pull WASM images from docker registries, and copy the WASM module to then be able to run it with a runtime like wasmtime, however I can't seem to find any API or example that shows how to copy from from a docker image layer. Is this possible, and if so would it be possible to add an example of how to do it?
So, you can download the contents of a container that was created from an image ... an example might be in the test https://github.com/fussybeaver/bollard/blob/master/tests/container_test.rs#L603-L612
Otherwise, there is also the buildkit functionality that will generate a file structure on build of an image.. https://github.com/moby/buildkit/blob/master/README.md#local-directory - that might be possible with some code changes, I'll need to take a closer look in the coming days.
This is the code I ended up using , I had to assume a default path of /entrypoint.wasm
because I couldn't figure out a way to create an image with a ENTRYPOINT
programmatically. It might be nice if there was a higher level equivalent to this in the library, so that you could have the equivalent of docker cp
as a function.
async fn pull_from_image(name: &str) -> Result<Vec<u8>, eyre::Error> {
let docker = Docker::connect_with_local_defaults()?;
// Pull the Docker image
let options = CreateImageOptions {
from_image: name.to_string(),
..<_>::default()
};
let mut stream = docker.create_image(Some(options), None, None);
while let Some(_) = stream.next().await {}
// Create a new container from the image
let options = CreateContainerOptions {
name: "my-container",
..<_>::default()
};
let container = docker
.create_container(
Some(options),
bollard::container::Config {
image: Some(name),
cmd: Some(vec!["/usr/bin/true"]),
..<_>::default()
},
)
.await?;
let _ = docker.start_container::<String>(&container.id, None).await;
// Inspect the container to retrieve the ENTRYPOINT value
let options = InspectContainerOptions { size: false };
let container_info = docker
.inspect_container(&container.id, Some(options))
.await?;
let entrypoint = match container_info.config {
Some(config) => match config.entrypoint {
Some(entrypoint) => entrypoint.join("/"),
None => "/entrypoint.wasm".into(),
},
None => return Err(eyre::eyre!("Failed to retrieve container configuration.")),
};
// Retrieve the WASM module from the container using the Docker API
let stream = docker.download_from_container(
&container.id,
Some(DownloadFromContainerOptions {
path: entrypoint.clone(),
}),
);
let bytes = stream
.collect::<Vec<_>>()
.await
.into_iter()
.collect::<Result<Vec<_>, _>>()?;
let bytes = bytes
.into_iter()
.fold(bytes::BytesMut::new(), |mut buf, item| {
buf.extend(item);
buf
});
// Remove the container
let options = RemoveContainerOptions {
v: true,
force: true,
link: false,
};
docker
.remove_container(&container.id, Some(options))
.await?;
// Decompress the tar archive in memory
let mut archive = Archive::new(Self::decompress_layer(&bytes)?);
let mut wasm_module = Vec::new();
for file in archive.entries()? {
let mut file = file?;
if std::path::Path::new("/").join(file.path()?) == std::path::Path::new(&entrypoint) {
file.read_to_end(&mut wasm_module)?;
break;
}
}
Ok(wasm_module.into())
}
fn decompress_layer(layer: &[u8]) -> Result<Box<dyn Read + '_>, eyre::Error> {
const GZIP: &[u8] = b"\x1F\x8B";
const BZIP2: &[u8] = b"\x42\x5A";
const XZ: &[u8] = b"\xFD\x37";
match &layer[0..2] {
GZIP => Ok(Box::from(GzDecoder::new(layer))),
BZIP2 => Ok(Box::from(BzDecoder::new(layer))),
XZ => Ok(Box::from(XzDecoder::new(layer))),
_ => Ok(Box::from(Cursor::new(layer))),
}
}