Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 5 additions & 8 deletions chi/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -560,7 +560,9 @@ def execute(container_ref: "str", command: "str") -> "dict":
Returns:
A summary of the output of the command, with "output" and "exit_code".
"""
return zun().containers.execute(container_ref, command=command, run=True)
result = zun().containers.execute_run(container_ref, command)
output = (result.stdout + result.stderr).decode(errors="replace")
return {"output": output, "exit_code": result.returncode}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also add a default param to split stdout and stderr.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, I think that's easy to do in a followup with some examples.



def upload(container_ref: "str", source: "str", dest: "str") -> "dict":
Expand Down Expand Up @@ -592,19 +594,14 @@ def download(container_ref: "str", source: "str", dest: "str"):

Download a file or directory from a running container.

This method requires your running container to include
both the POSIX sh and GNU tar utilities.
This method requires your running container to include tar.

Args:
container_ref (str): The name or ID of the container.
source (str): The (container) path of the file or directory.
dest (str): The (local) path to download to.
"""
res = zun().containers.get_archive(container_ref, source)
fd = io.BytesIO(res["data"])
with tarfile.open(fileobj=fd, mode="r") as tar:
tar.extraction_filter = lambda member, path: member
tar.extractall(dest)
zun().containers.copy_from(container_ref, source, dest)


def wait_for_active(container_ref: "str", timeout: int = (60 * 2)) -> "Container":
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ python-manilaclient
python-neutronclient
python-novaclient
python-swiftclient
chameleon-zunclient>=2025.1.1
chameleon-zunclient>=2025.1.2
ipython
ipydatagrid
ipywidgets
Expand Down
42 changes: 19 additions & 23 deletions tests/test_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import pytest
from zunclient.exceptions import Conflict

from chi.container import Container, download, upload
from chi.container import Container, download, execute, upload


@pytest.fixture()
Expand Down Expand Up @@ -88,29 +88,25 @@ def test_upload_creates_tar_and_calls_put_archive(mocker):
assert os.path.basename(tmpfile.name) in names


def test_download_extracts_tar_and_writes_file(mocker):
# Patch zun client
def test_execute_uses_execute_run(mocker):
zun_mock = mocker.patch("chi.container.zun")()
result = zun_mock.containers.execute_run.return_value
result.stdout = b"out\n"
result.stderr = b"err\n"
result.returncode = 3

assert execute("container_id", "ls -l") == {"output": "out\nerr\n", "exit_code": 3}
zun_mock.containers.execute_run.assert_called_once_with("container_id", "ls -l")


def test_download_uses_copy_from(mocker):
zun_mock = mocker.patch("chi.container.zun")()
# Create a tar archive in memory with a test file
file_content = b"test content"
file_name = "testfile.txt"
tar_bytes_io = io.BytesIO()
with tarfile.open(fileobj=tar_bytes_io, mode="w") as tar:
info = tarfile.TarInfo(name=file_name)
info.size = len(file_content)
tar.addfile(info, io.BytesIO(file_content))
tar_bytes = tar_bytes_io.getvalue()
zun_mock.containers.get_archive.return_value = {"data": tar_bytes}

# Use a temporary directory for extraction
with tempfile.TemporaryDirectory() as tmpdir:
dest_path = os.path.join(tmpdir, file_name)

download("container_id", file_name, tmpdir)
# Check that the file was extracted
assert os.path.exists(dest_path)
with open(dest_path, "rb") as f:
assert f.read() == file_content

download("container_id", "/root/data", "/tmp/dest")

zun_mock.containers.copy_from.assert_called_once_with(
"container_id", "/root/data", "/tmp/dest"
)


def test_delete_calls_force(mocker):
Expand Down
Loading