atlas-runner 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ATTMOS Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,22 @@
1
+ Metadata-Version: 2.4
2
+ Name: atlas-runner
3
+ Version: 0.1.0
4
+ Summary: A runtime abstraction layer for ATLAS — container orchestration via Docker with a pluggable backend interface.
5
+ Author: ATTMOS Inc.
6
+ License-Expression: MIT
7
+ Project-URL: Repository, https://github.com/ATTMOS/atlas-runner
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Requires-Python: >=3.11
13
+ License-File: LICENSE
14
+ Requires-Dist: pydantic<3,>=2.5
15
+ Requires-Dist: typer<1,>=0.9
16
+ Requires-Dist: rich<14,>=13.0
17
+ Provides-Extra: dev
18
+ Requires-Dist: pytest>=8.0; extra == "dev"
19
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
20
+ Requires-Dist: ruff>=0.3; extra == "dev"
21
+ Requires-Dist: mypy>=1.8; extra == "dev"
22
+ Dynamic: license-file
@@ -0,0 +1,115 @@
1
+ # atlas-runner
2
+
3
+ A runtime abstraction layer for ATLAS. Provides a library and CLI that launches and manages workloads using Docker (v0), with a backend interface designed to support Apptainer/Singularity in future releases. Used by atlas-agent for deterministic container execution, lifecycle control, status, logs, and resource enforcement.
4
+
5
+ ## Requirements
6
+
7
+ - Python >= 3.11
8
+ - Docker Engine (accessible via `docker` CLI)
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ pip install atlas-runner
14
+ ```
15
+
16
+ For development:
17
+
18
+ ```bash
19
+ pip install -e ".[dev]"
20
+ ```
21
+
22
+ ## Quickstart
23
+
24
+ ### Library usage
25
+
26
+ ```python
27
+ from atlas_runner import Runner, RunSpec, create_backend
28
+
29
+ # Create a runner with the Docker backend
30
+ runner = Runner(create_backend("docker"))
31
+
32
+ # Pull an image and run a container
33
+ runner.pull("alpine:latest")
34
+ result = runner.run(RunSpec(
35
+ name="my-task",
36
+ image="alpine:latest",
37
+ command=["echo", "hello"],
38
+ ))
39
+ print(result.container_id, result.started_at)
40
+
41
+ # Check status and fetch logs
42
+ status = runner.status("my-task")
43
+ print(status.state, status.exit_code)
44
+ logs = runner.logs("my-task")
45
+
46
+ # Clean up
47
+ runner.destroy("my-task")
48
+ ```
49
+
50
+ ### CLI usage
51
+
52
+ ```bash
53
+ # Authenticate with a registry
54
+ atlas-runner login registry.io -u user --password-stdin < token.txt
55
+
56
+ # Pull an image
57
+ atlas-runner pull alpine:latest
58
+
59
+ # Run a container with resource limits
60
+ atlas-runner run --name my-task --image alpine:latest \
61
+ --cmd echo --cmd hello \
62
+ --cpu-cores 4 --memory-gb 16
63
+
64
+ # Run with GPU access
65
+ atlas-runner run --name gpu-task --image nvidia/cuda:12.0-base \
66
+ --gpu 0 --gpu 1 \
67
+ --shell "python train.py"
68
+
69
+ # Check status (human-readable or JSON)
70
+ atlas-runner status my-task
71
+ atlas-runner --json status my-task
72
+
73
+ # View logs
74
+ atlas-runner logs my-task
75
+
76
+ # List all containers
77
+ atlas-runner ps
78
+ atlas-runner --json ps
79
+
80
+ # Stop and remove
81
+ atlas-runner destroy my-task
82
+ ```
83
+
84
+ ## Configuration
85
+
86
+ | Option | CLI Flag | Description |
87
+ |---|---|---|
88
+ | Backend | `--backend docker` | Container runtime backend (default: `docker`) |
89
+ | Docker host | `--docker-host tcp://remote:2375` | Docker daemon socket URL |
90
+ | JSON output | `--json` | Output structured JSON instead of human-readable text |
91
+
92
+ ## Development
93
+
94
+ ```bash
95
+ # Install with dev dependencies
96
+ pip install -e ".[dev]"
97
+
98
+ # Run tests
99
+ pytest tests/ -v
100
+
101
+ # Lint and format
102
+ ruff check src/ tests/
103
+ ruff format --check src/ tests/
104
+
105
+ # Type check
106
+ mypy src/
107
+ ```
108
+
109
+ ## API Reference
110
+
111
+ See [docs/api.md](docs/api.md) for the full API reference.
112
+
113
+ ## License
114
+
115
+ MIT — see [LICENSE](LICENSE) for details.
@@ -0,0 +1,52 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "atlas-runner"
7
+ description = "A runtime abstraction layer for ATLAS — container orchestration via Docker with a pluggable backend interface."
8
+ version = "0.1.0"
9
+ requires-python = ">=3.11"
10
+ license = "MIT"
11
+ authors = [{ name = "ATTMOS Inc." }]
12
+ classifiers = [
13
+ "Development Status :: 3 - Alpha",
14
+ "Programming Language :: Python :: 3.11",
15
+ "Programming Language :: Python :: 3.12",
16
+ "Programming Language :: Python :: 3.13",
17
+ ]
18
+ dependencies = [
19
+ "pydantic>=2.5,<3",
20
+ "typer>=0.9,<1",
21
+ "rich>=13.0,<14",
22
+ ]
23
+
24
+ [project.optional-dependencies]
25
+ dev = [
26
+ "pytest>=8.0",
27
+ "pytest-cov>=4.0",
28
+ "ruff>=0.3",
29
+ "mypy>=1.8",
30
+ ]
31
+
32
+ [project.urls]
33
+ Repository = "https://github.com/ATTMOS/atlas-runner"
34
+
35
+ [project.scripts]
36
+ atlas-runner = "atlas_runner.cli:app"
37
+
38
+ [tool.setuptools.packages.find]
39
+ where = ["src"]
40
+
41
+ [tool.ruff]
42
+ target-version = "py311"
43
+ src = ["src", "tests"]
44
+
45
+ [tool.ruff.lint]
46
+ select = ["E", "F", "I", "UP"]
47
+
48
+ [tool.mypy]
49
+ python_version = "3.11"
50
+ strict = true
51
+ packages = ["atlas_runner"]
52
+ mypy_path = "src"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,27 @@
1
+ """Atlas Runner — container orchestration via Docker."""
2
+
3
+ from atlas_runner._version import __version__
4
+ from atlas_runner.errors import ErrorCode, RunnerError
5
+ from atlas_runner.models import (
6
+ ContainerState,
7
+ ContainerStatus,
8
+ Mount,
9
+ RunResult,
10
+ RunSpec,
11
+ )
12
+ from atlas_runner.protocol import RunnerBackend
13
+ from atlas_runner.runner import Runner, create_backend
14
+
15
+ __all__ = [
16
+ "__version__",
17
+ "ContainerState",
18
+ "ContainerStatus",
19
+ "create_backend",
20
+ "ErrorCode",
21
+ "Mount",
22
+ "Runner",
23
+ "RunnerBackend",
24
+ "RunnerError",
25
+ "RunResult",
26
+ "RunSpec",
27
+ ]
@@ -0,0 +1,146 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import re
5
+ import subprocess
6
+ from typing import Any
7
+
8
+ from atlas_runner.errors import ErrorCode, RunnerError
9
+
10
+ _DAEMON_MARKERS = (
11
+ "cannot connect to the docker daemon",
12
+ "is the docker daemon running",
13
+ "connection refused",
14
+ )
15
+
16
+
17
+ class DockerCLI:
18
+ """Low-level wrapper around the ``docker`` CLI.
19
+
20
+ Every Docker operation in atlas-runner goes through this class so that
21
+ ``subprocess.run`` is isolated to a single module (clean mock boundary).
22
+ """
23
+
24
+ def __init__(self, docker_host: str | None = None) -> None:
25
+ self._base: list[str] = ["docker"]
26
+ if docker_host is not None:
27
+ self._base = ["docker", "--host", docker_host]
28
+
29
+ # ------------------------------------------------------------------
30
+ # Core executor
31
+ # ------------------------------------------------------------------
32
+
33
+ def _run(
34
+ self,
35
+ args: list[str],
36
+ *,
37
+ check: bool = True,
38
+ capture_output: bool = True,
39
+ input_data: str | None = None,
40
+ timeout: int | None = None,
41
+ ) -> subprocess.CompletedProcess[str]:
42
+ cmd = self._base + args
43
+ try:
44
+ result = subprocess.run(
45
+ cmd,
46
+ check=False,
47
+ capture_output=capture_output,
48
+ text=True,
49
+ input=input_data,
50
+ timeout=timeout,
51
+ )
52
+ except subprocess.TimeoutExpired as exc:
53
+ raise RunnerError(
54
+ code=ErrorCode.BACKEND_ERROR,
55
+ message=f"Docker command timed out after {timeout}s",
56
+ details={"command": cmd, "timeout": timeout},
57
+ ) from exc
58
+
59
+ if check and result.returncode != 0:
60
+ stderr = result.stderr.strip() if result.stderr else ""
61
+ if any(m in stderr.lower() for m in _DAEMON_MARKERS):
62
+ raise RunnerError(
63
+ code=ErrorCode.DOCKER_DAEMON_UNAVAILABLE,
64
+ message="Docker daemon is not available",
65
+ details={"stderr": stderr},
66
+ )
67
+ raise RunnerError(
68
+ code=ErrorCode.BACKEND_ERROR,
69
+ message=f"Docker command failed (exit {result.returncode})",
70
+ details={"command": cmd, "stderr": stderr},
71
+ )
72
+
73
+ return result
74
+
75
+ # ------------------------------------------------------------------
76
+ # Public methods
77
+ # ------------------------------------------------------------------
78
+
79
+ def login(
80
+ self,
81
+ registry: str,
82
+ username: str,
83
+ password: str,
84
+ ) -> None:
85
+ try:
86
+ self._run(
87
+ ["login", "--username", username, "--password-stdin", registry],
88
+ input_data=password,
89
+ )
90
+ except RunnerError as exc:
91
+ if exc.code == ErrorCode.BACKEND_ERROR:
92
+ raise RunnerError(
93
+ code=ErrorCode.LOGIN_FAILED,
94
+ message=f"Login failed for registry: {registry}",
95
+ details=exc.details,
96
+ ) from exc
97
+ raise
98
+
99
+ def pull(self, image: str) -> None:
100
+ try:
101
+ self._run(["pull", image])
102
+ except RunnerError as exc:
103
+ if exc.code == ErrorCode.BACKEND_ERROR:
104
+ raise RunnerError(
105
+ code=ErrorCode.IMAGE_PULL_FAILED,
106
+ message=f"Failed to pull image: {image}",
107
+ details=exc.details,
108
+ ) from exc
109
+ raise
110
+
111
+ def run(self, args: list[str]) -> str:
112
+ result = self._run(["run"] + args)
113
+ return result.stdout.strip()
114
+
115
+ def stop(self, name: str) -> None:
116
+ self._run(["stop", name], check=False)
117
+
118
+ def rm(self, name: str) -> None:
119
+ self._run(["rm", name], check=False)
120
+
121
+ def inspect(self, name: str) -> dict[str, Any]:
122
+ result = self._run(["inspect", name])
123
+ data: list[dict[str, Any]] = json.loads(result.stdout)
124
+ return data[0]
125
+
126
+ def ps(self, filters: list[str] | None = None) -> list[dict[str, Any]]:
127
+ args = ["ps", "--format", "{{json .}}", "--no-trunc"]
128
+ for f in filters or []:
129
+ args.extend(["--filter", f])
130
+ result = self._run(args)
131
+ entries: list[dict[str, Any]] = []
132
+ for line in result.stdout.strip().splitlines():
133
+ if line:
134
+ entries.append(json.loads(line))
135
+ return entries
136
+
137
+ def logs(self, name: str) -> str:
138
+ result = self._run(["logs", name])
139
+ return result.stdout
140
+
141
+ def container_exists(self, name: str) -> bool:
142
+ escaped = re.escape(name)
143
+ result = self._run(
144
+ ["ps", "-a", "--filter", f"name=^/{escaped}$", "--format", "{{.ID}}"],
145
+ )
146
+ return bool(result.stdout.strip())
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"