A Python client library for Enroot, providing a docker-py inspired interface for managing Enroot containers and images.
enroot-py offers a Pythonic API to interact with Enroot, NVIDIA's container runtime for HPC environments. It provides a familiar interface similar to docker-py, making it easy to manage containers, pull images, and execute commands within Enroot containers.
- Image Management: Pull, list, and retrieve Docker images converted to Enroot's
.sqshformat - Container Operations: Create, start, stop, remove, and execute commands in containers
- Port Mapping: Automatic port allocation and mapping for containerized services
- Environment Variables: Pass environment variables to containers
- Resource Limits: CPU and memory limits via
systemd-run(when available) - Docker Compatibility: Automatically imports Docker images if not found locally
- Python 3.8 or higher
- Enroot installed and available in your
PATH - Linux operating system (Enroot is Linux-only)
This library is not yet packaged on PyPI. To use it, clone the repository and add it to your PYTHONPATH:
git clone https://github.com/your-username/enroot-py.git
cd enroot-py
export PYTHONPATH=$PYTHONPATH:$(pwd)Alternatively, you can install it in development mode:
pip install -e .from enroot.client import from_env
client = from_env()
# Check if Enroot is available
if client.ping():
print("Enroot is running!")Pull a Docker image and convert it to Enroot's .sqsh format:
# Pull with explicit tag
image = client.images.pull("ubuntu", "20.04")
print(f"Pulled image: {image.id}")
# Pull latest tag (default)
image = client.images.pull("python", "3.9")
# Pull from a custom registry
image = client.images.pull("my-image", "latest", registry_host="registry.example.com")images = client.images.list()
for image in images:
print(f"Image: {image.id}, Tags: {image.tags}")image = client.images.get("ubuntu+20.04") # Use '+' instead of '/' or ':'
print(f"Found image: {image.id}")container = client.containers.run(
"ubuntu:20.04", # Image name (will be imported if needed)
command="sleep 3600", # Command to run
name="my-container", # Optional container name
detach=True, # Required: must be True
ports={"8080/tcp": "8080"}, # Port mapping (container_port: host_port)
environment={ # Environment variables
"MY_VAR": "my_value",
"DEBUG": "1"
},
cpu_count=2.0, # Optional: CPU limit (requires systemd-run)
mem_limit="2G" # Optional: Memory limit (requires systemd-run)
)
print(f"Started container: {container.name}")
print(f"Status: {container.status}")Note: detach=True is required. Non-detached mode is not currently supported.
You can also use local .sqsh files directly:
container = client.containers.run(
"/path/to/image.sqsh",
command="bash",
detach=True
)containers = client.containers.list()
for container in containers:
print(f"- {container.name} ({container.status})")container = client.containers.get("my-container")
print(f"Container status: {container.status}")
print(f"Container attributes: {container.attrs}")result = container.exec_run("echo 'Hello from container!'")
print(f"Exit code: {result.exit_code}")
print(f"Output: {result.output}")
# Execute with a list of arguments
result = container.exec_run(["ls", "-la", "/tmp"])# Stop (kill) a container
container.kill()
# Remove a container
container.remove()
# Force remove a container
container.remove(force=True)# Reload status from Enroot
container.reload()
# Get current status
status = container.status # "running" or "exited"
# Get container attributes (including port mappings)
attrs = container.attrsENROOT_HOME: Override the default Enroot home directory (default:~/.cache/enroot)ENROOT_DEBUG: Set to"1"to enable debug output (shows Enroot command execution)XDG_CACHE_HOME: Used for cache directory ifENROOT_HOMEis not set
When you specify ports in containers.run(), the library automatically allocates a free port on the host and maps it to the container port. The allocated port is available in the container's attrs:
container = client.containers.run(
"nginx",
detach=True,
ports={"80/tcp": "80"}
)
# Access port mapping
port_mapping = container.attrs["NetworkSettings"]["Ports"]
# The actual host port is stored hereThe allocated port is also set as the PORT environment variable inside the container.
CPU and memory limits are applied using systemd-run when available:
container = client.containers.run(
"my-image",
detach=True,
cpu_count=1.5, # 1.5 CPUs
mem_limit="512M" # 512 MB memory limit
)Note: Resource limits require systemd-run to be available. If it's not found, a warning is issued and the limits are ignored.
Main client class for interacting with Enroot.
ping() -> bool: Check if Enroot is availableimages: Access to image operations (Imagesinstance)containers: Access to container operations (Containersinstance)
Image management operations.
pull(repository: str, tag: str | None = None, registry_host: str = "docker.io") -> Image: Pull and import a Docker imagelist() -> List[Image]: List all available imagesget(ref: str) -> Image: Get a specific image by reference
Container management operations.
run(image, command=None, name=None, *, detach=False, remove=False, ports=None, environment=None, cpu_count=None, mem_limit=None, cpus=None, **_) -> Container: Run a new containerlist() -> List[Container]: List all containersget(ident: str) -> Container: Get a container by name
Represents a single container instance.
name: str: Container namestatus: str: Container status ("running" or "exited")attrs: Dict: Container attributes (similar to Docker API)reload(): Reload container status from Enrootkill(**_): Stop and remove the containerremove(force: bool = False, **_): Remove the containerexec_run(cmd: str | List[str], **_) -> ExecResult: Execute a command in the container
Represents a container image.
id: str: Image ID (path to .sqsh file)tags: List[str]: Image tags
- Detached Mode Only: The
detachparameter must beTrue. Non-detached containers are not supported. - No Logs: The
logs()method returns an empty bytes object. Enroot doesn't provide a built-in logging mechanism. - Resource Limits: CPU and memory limits require
systemd-runto be available on the system. - Port Mapping: All ports are mapped to a single automatically-allocated free port. Individual port mappings are not fully supported.
Run the test suite:
python -m unittest discover testsOr run individual test files:
python -m unittest tests.test_client
python -m unittest tests.test_container
python -m unittest tests.test_image
python -m unittest tests.test_utilsContributions are welcome! Please feel free to submit a Pull Request.
MIT License (see pyproject.toml for details)