fussybeaver / bollard

Docker daemon API in Rust

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Copy entrypoint from image

XAMPPRocky opened this issue Β· comments

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))),
        }
    }