axio-tools-docker 0.8.0__tar.gz → 0.9.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.
- {axio_tools_docker-0.8.0 → axio_tools_docker-0.9.0}/PKG-INFO +68 -18
- {axio_tools_docker-0.8.0 → axio_tools_docker-0.9.0}/README.md +67 -17
- axio_tools_docker-0.9.0/conftest.py +28 -0
- {axio_tools_docker-0.8.0 → axio_tools_docker-0.9.0}/pyproject.toml +1 -1
- {axio_tools_docker-0.8.0 → axio_tools_docker-0.9.0}/src/axio_tools_docker/sandbox.py +126 -140
- axio_tools_docker-0.9.0/tests/__init__.py +0 -0
- {axio_tools_docker-0.8.0 → axio_tools_docker-0.9.0}/tests/test_integration.py +119 -161
- {axio_tools_docker-0.8.0 → axio_tools_docker-0.9.0}/tests/test_sandbox.py +69 -4
- {axio_tools_docker-0.8.0 → axio_tools_docker-0.9.0}/.github/workflows/publish.yml +0 -0
- {axio_tools_docker-0.8.0 → axio_tools_docker-0.9.0}/.github/workflows/tests.yml +0 -0
- {axio_tools_docker-0.8.0 → axio_tools_docker-0.9.0}/.gitignore +0 -0
- {axio_tools_docker-0.8.0 → axio_tools_docker-0.9.0}/LICENSE +0 -0
- {axio_tools_docker-0.8.0 → axio_tools_docker-0.9.0}/Makefile +0 -0
- {axio_tools_docker-0.8.0 → axio_tools_docker-0.9.0}/src/axio_tools_docker/__init__.py +0 -0
- {axio_tools_docker-0.8.0 → axio_tools_docker-0.9.0}/src/axio_tools_docker/py.typed +0 -0
- {axio_tools_docker-0.8.0 → axio_tools_docker-0.9.0}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: axio-tools-docker
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: Docker sandbox tools for Axio
|
|
5
5
|
Project-URL: Homepage, https://github.com/mosquito/axio-agent
|
|
6
6
|
Project-URL: Repository, https://github.com/mosquito/axio-agent
|
|
@@ -22,7 +22,7 @@ Docker sandbox tools for [axio](https://github.com/mosquito/axio-agent).
|
|
|
22
22
|
|
|
23
23
|
`DockerSandbox` is an async context manager that spins up an isolated Docker
|
|
24
24
|
container on entry and removes it on exit. Inside the context it exposes six
|
|
25
|
-
`axio` tools
|
|
25
|
+
`axio` tools - the same `shell`, `write_file`, `read_file`, `list_files`,
|
|
26
26
|
`run_python`, and `patch_file` as `axio-tools-local`, but every operation runs
|
|
27
27
|
inside the container, never on the host.
|
|
28
28
|
|
|
@@ -35,7 +35,7 @@ docker info # should succeed
|
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
The package talks to the Docker Engine API directly via
|
|
38
|
-
[aiodocker](https://aiodocker.readthedocs.io/)
|
|
38
|
+
[aiodocker](https://aiodocker.readthedocs.io/) - the `docker` CLI is not
|
|
39
39
|
required.
|
|
40
40
|
|
|
41
41
|
## Installation
|
|
@@ -46,7 +46,7 @@ pip install axio-tools-docker
|
|
|
46
46
|
|
|
47
47
|
## Quick start
|
|
48
48
|
|
|
49
|
-
<!-- name: test_readme_usage;
|
|
49
|
+
<!-- name: test_readme_usage; fixtures: docker -->
|
|
50
50
|
```python
|
|
51
51
|
import asyncio
|
|
52
52
|
from axio.agent import Agent
|
|
@@ -71,7 +71,7 @@ asyncio.run(main())
|
|
|
71
71
|
|
|
72
72
|
## Sandbox tools
|
|
73
73
|
|
|
74
|
-
These mirror `axio-tools-local` exactly
|
|
74
|
+
These mirror `axio-tools-local` exactly - same names and field schemas:
|
|
75
75
|
|
|
76
76
|
| Tool | Description |
|
|
77
77
|
|---|---|
|
|
@@ -88,9 +88,18 @@ The container is created on `__aenter__` and removed on `__aexit__` (`docker rm
|
|
|
88
88
|
Cleanup runs even when the body raises an exception:
|
|
89
89
|
|
|
90
90
|
```python
|
|
91
|
+
from axio_tools_docker import DockerSandbox
|
|
92
|
+
from axio import Agent
|
|
93
|
+
|
|
91
94
|
async def run(ctx):
|
|
95
|
+
agent = Agent(
|
|
96
|
+
system="You are a coding assistant. Use the sandbox tools to run code safely.",
|
|
97
|
+
tools=ctx.sandbox.tools,
|
|
98
|
+
)
|
|
99
|
+
|
|
92
100
|
async with DockerSandbox(image="alpine:latest") as sandbox:
|
|
93
101
|
await agent.run("...", ctx)
|
|
102
|
+
|
|
94
103
|
# container removed here (unless remove=False)
|
|
95
104
|
```
|
|
96
105
|
|
|
@@ -108,7 +117,7 @@ The running container's ID is available as `sandbox.container_id` inside the
|
|
|
108
117
|
|
|
109
118
|
Pass `name=` to give the container a fixed name. If a running container with
|
|
110
119
|
that name already exists, the sandbox attaches to it instead of creating a new
|
|
111
|
-
one, and never removes it on exit
|
|
120
|
+
one, and never removes it on exit - regardless of `remove`:
|
|
112
121
|
|
|
113
122
|
```python
|
|
114
123
|
import asyncio
|
|
@@ -126,37 +135,76 @@ asyncio.run(first_session())
|
|
|
126
135
|
asyncio.run(second_session())
|
|
127
136
|
```
|
|
128
137
|
|
|
138
|
+
## Named volumes
|
|
139
|
+
|
|
140
|
+
Named volumes are managed by the Docker daemon and persist across container
|
|
141
|
+
restarts. Use them to share state between sandbox sessions:
|
|
142
|
+
|
|
143
|
+
<!-- name: test_readme_example; fixtures: docker -->
|
|
144
|
+
```python
|
|
145
|
+
import asyncio
|
|
146
|
+
from axio_tools_docker import DockerSandbox
|
|
147
|
+
|
|
148
|
+
async def main() -> None:
|
|
149
|
+
async with DockerSandbox(
|
|
150
|
+
image="python:3.12-alpine",
|
|
151
|
+
named_volumes={"/data": "my-project-data"},
|
|
152
|
+
) as sb:
|
|
153
|
+
await sb.write_file("/data/state.json", '{"count": 1}')
|
|
154
|
+
# Container removed, volume survives.
|
|
155
|
+
|
|
156
|
+
async with DockerSandbox(
|
|
157
|
+
image="python:3.12-alpine",
|
|
158
|
+
named_volumes={"/data": "my-project-data"},
|
|
159
|
+
volumes_remove=True, # delete the volume on exit
|
|
160
|
+
) as sb:
|
|
161
|
+
raw = await sb.read_file_bytes("/data/state.json")
|
|
162
|
+
assert raw.decode() == '{"count": 1}'
|
|
163
|
+
|
|
164
|
+
asyncio.run(main())
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Docker creates the volume automatically if it does not exist yet.
|
|
168
|
+
|
|
129
169
|
## Configuration
|
|
130
170
|
|
|
131
|
-
<!-- name: test_readme_config -->
|
|
171
|
+
<!-- name: test_readme_config; fixtures: docker -->
|
|
132
172
|
```python
|
|
173
|
+
import os
|
|
133
174
|
from axio_tools_docker import DockerSandbox
|
|
134
175
|
|
|
135
176
|
sandbox = DockerSandbox(
|
|
136
|
-
"unix:///var/run/docker.sock",
|
|
177
|
+
os.getenv("DOCKER_HOST", "unix:///var/run/docker.sock"), # Docker daemon URL, optional
|
|
137
178
|
image="python:3.12-slim",
|
|
138
|
-
memory="512m",
|
|
139
|
-
cpus="2.0",
|
|
140
|
-
network=False,
|
|
179
|
+
memory="512m", # memory limit: "256m", "1g", …
|
|
180
|
+
cpus="2.0", # CPU limit
|
|
181
|
+
network=False, # False=none, True=default, str=explicit mode
|
|
141
182
|
workdir="/workspace",
|
|
142
|
-
volumes={"/workspace": "/tmp/host-dir"},
|
|
183
|
+
volumes={"/workspace": "/tmp/host-dir"}, # {container_path: host_path}
|
|
184
|
+
named_volumes={"/data": "my-project-data"}, # named Docker volumes
|
|
185
|
+
volumes_remove=False, # remove named volumes on exit
|
|
143
186
|
env={"PYTHONPATH": "/app"},
|
|
144
187
|
user="nobody",
|
|
145
188
|
name="my-agent-sandbox",
|
|
146
189
|
remove=False,
|
|
147
|
-
read_only=True,
|
|
148
|
-
shm_size="64m",
|
|
149
|
-
cap_add=["NET_ADMIN"],
|
|
150
|
-
cap_drop=["ALL"],
|
|
190
|
+
read_only=True, # read-only root filesystem
|
|
191
|
+
shm_size="64m", # /dev/shm size
|
|
192
|
+
cap_add=["NET_ADMIN"], # add Linux capabilities
|
|
193
|
+
cap_drop=["ALL"], # drop Linux capabilities
|
|
151
194
|
privileged=False,
|
|
152
195
|
ulimits={"nofile": (1024, 65536), "nproc": 512},
|
|
153
196
|
tmpfs={"/tmp": "size=128m,mode=1777"},
|
|
154
|
-
ports={8080: 8080},
|
|
197
|
+
ports={8080: 8080}, # {container_port: host_port}
|
|
155
198
|
platform="linux/amd64",
|
|
156
199
|
extra_hosts={"host.docker.internal": "host-gateway"},
|
|
157
200
|
devices=["/dev/net/tun", "/dev/sda:/dev/xvda:r"],
|
|
158
201
|
dns=["8.8.8.8", "1.1.1.1"],
|
|
159
202
|
)
|
|
203
|
+
|
|
204
|
+
assert sandbox.image == "python:3.12-slim"
|
|
205
|
+
assert sandbox.memory == "512m"
|
|
206
|
+
assert sandbox.cpus == "2.0"
|
|
207
|
+
assert sandbox.network == False
|
|
160
208
|
```
|
|
161
209
|
|
|
162
210
|
| Parameter | Type | Default | Description |
|
|
@@ -168,6 +216,8 @@ sandbox = DockerSandbox(
|
|
|
168
216
|
| `network` | `bool \| str` | `False` | Network mode. `False` → `none`. `True` → Docker default. String → explicit `NetworkMode` (e.g. `"host"`, `"bridge"`, `"my-net"`). |
|
|
169
217
|
| `workdir` | `str` | `"/workspace"` | Working directory inside the container. |
|
|
170
218
|
| `volumes` | `dict[str, str]` | `{}` | Bind mounts as `{container_path: host_path}`. |
|
|
219
|
+
| `named_volumes` | `dict[str, str]` | `{}` | Named Docker volumes as `{container_path: volume_name}`. Created automatically if absent. |
|
|
220
|
+
| `volumes_remove` | `bool` | `False` | Remove named volumes on exit. No effect when attached to an existing container. |
|
|
171
221
|
| `env` | `dict[str, str]` | `{}` | Environment variables passed to all commands. |
|
|
172
222
|
| `user` | `str` | `""` | User to run as (e.g. `"nobody"`, `"1000"`). |
|
|
173
223
|
| `name` | `str` | `""` | Container name. Attaches to existing container if found; creates new one otherwise. |
|
|
@@ -176,7 +226,7 @@ sandbox = DockerSandbox(
|
|
|
176
226
|
| `shm_size` | `str` | `""` | `/dev/shm` size (e.g. `"64m"`). Useful for PyTorch / shared-memory IPC. |
|
|
177
227
|
| `cap_add` | `list[str]` | `[]` | Linux capabilities to add (e.g. `["NET_ADMIN", "SYS_PTRACE"]`). |
|
|
178
228
|
| `cap_drop` | `list[str]` | `[]` | Linux capabilities to drop (e.g. `["ALL"]`). |
|
|
179
|
-
| `privileged` | `bool` | `False` | Extended privileges
|
|
229
|
+
| `privileged` | `bool` | `False` | Extended privileges - full capability set and device access. Use with care. |
|
|
180
230
|
| `ulimits` | `dict[str, int \| tuple[int, int]]` | `{}` | Resource limits. `{"nofile": 1024}` → soft=hard=1024. `{"nofile": (1024, 65536)}` → soft/hard split. |
|
|
181
231
|
| `tmpfs` | `dict[str, str]` | `{}` | Tmpfs mounts as `{path: options}` (e.g. `{"/tmp": "size=128m,mode=1777"}`). |
|
|
182
232
|
| `ports` | `dict[int, int]` | `{}` | Port bindings as `{container_port: host_port}`. Only meaningful when `network != False`. |
|
|
@@ -8,7 +8,7 @@ Docker sandbox tools for [axio](https://github.com/mosquito/axio-agent).
|
|
|
8
8
|
|
|
9
9
|
`DockerSandbox` is an async context manager that spins up an isolated Docker
|
|
10
10
|
container on entry and removes it on exit. Inside the context it exposes six
|
|
11
|
-
`axio` tools
|
|
11
|
+
`axio` tools - the same `shell`, `write_file`, `read_file`, `list_files`,
|
|
12
12
|
`run_python`, and `patch_file` as `axio-tools-local`, but every operation runs
|
|
13
13
|
inside the container, never on the host.
|
|
14
14
|
|
|
@@ -21,7 +21,7 @@ docker info # should succeed
|
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
The package talks to the Docker Engine API directly via
|
|
24
|
-
[aiodocker](https://aiodocker.readthedocs.io/)
|
|
24
|
+
[aiodocker](https://aiodocker.readthedocs.io/) - the `docker` CLI is not
|
|
25
25
|
required.
|
|
26
26
|
|
|
27
27
|
## Installation
|
|
@@ -32,7 +32,7 @@ pip install axio-tools-docker
|
|
|
32
32
|
|
|
33
33
|
## Quick start
|
|
34
34
|
|
|
35
|
-
<!-- name: test_readme_usage;
|
|
35
|
+
<!-- name: test_readme_usage; fixtures: docker -->
|
|
36
36
|
```python
|
|
37
37
|
import asyncio
|
|
38
38
|
from axio.agent import Agent
|
|
@@ -57,7 +57,7 @@ asyncio.run(main())
|
|
|
57
57
|
|
|
58
58
|
## Sandbox tools
|
|
59
59
|
|
|
60
|
-
These mirror `axio-tools-local` exactly
|
|
60
|
+
These mirror `axio-tools-local` exactly - same names and field schemas:
|
|
61
61
|
|
|
62
62
|
| Tool | Description |
|
|
63
63
|
|---|---|
|
|
@@ -74,9 +74,18 @@ The container is created on `__aenter__` and removed on `__aexit__` (`docker rm
|
|
|
74
74
|
Cleanup runs even when the body raises an exception:
|
|
75
75
|
|
|
76
76
|
```python
|
|
77
|
+
from axio_tools_docker import DockerSandbox
|
|
78
|
+
from axio import Agent
|
|
79
|
+
|
|
77
80
|
async def run(ctx):
|
|
81
|
+
agent = Agent(
|
|
82
|
+
system="You are a coding assistant. Use the sandbox tools to run code safely.",
|
|
83
|
+
tools=ctx.sandbox.tools,
|
|
84
|
+
)
|
|
85
|
+
|
|
78
86
|
async with DockerSandbox(image="alpine:latest") as sandbox:
|
|
79
87
|
await agent.run("...", ctx)
|
|
88
|
+
|
|
80
89
|
# container removed here (unless remove=False)
|
|
81
90
|
```
|
|
82
91
|
|
|
@@ -94,7 +103,7 @@ The running container's ID is available as `sandbox.container_id` inside the
|
|
|
94
103
|
|
|
95
104
|
Pass `name=` to give the container a fixed name. If a running container with
|
|
96
105
|
that name already exists, the sandbox attaches to it instead of creating a new
|
|
97
|
-
one, and never removes it on exit
|
|
106
|
+
one, and never removes it on exit - regardless of `remove`:
|
|
98
107
|
|
|
99
108
|
```python
|
|
100
109
|
import asyncio
|
|
@@ -112,37 +121,76 @@ asyncio.run(first_session())
|
|
|
112
121
|
asyncio.run(second_session())
|
|
113
122
|
```
|
|
114
123
|
|
|
124
|
+
## Named volumes
|
|
125
|
+
|
|
126
|
+
Named volumes are managed by the Docker daemon and persist across container
|
|
127
|
+
restarts. Use them to share state between sandbox sessions:
|
|
128
|
+
|
|
129
|
+
<!-- name: test_readme_example; fixtures: docker -->
|
|
130
|
+
```python
|
|
131
|
+
import asyncio
|
|
132
|
+
from axio_tools_docker import DockerSandbox
|
|
133
|
+
|
|
134
|
+
async def main() -> None:
|
|
135
|
+
async with DockerSandbox(
|
|
136
|
+
image="python:3.12-alpine",
|
|
137
|
+
named_volumes={"/data": "my-project-data"},
|
|
138
|
+
) as sb:
|
|
139
|
+
await sb.write_file("/data/state.json", '{"count": 1}')
|
|
140
|
+
# Container removed, volume survives.
|
|
141
|
+
|
|
142
|
+
async with DockerSandbox(
|
|
143
|
+
image="python:3.12-alpine",
|
|
144
|
+
named_volumes={"/data": "my-project-data"},
|
|
145
|
+
volumes_remove=True, # delete the volume on exit
|
|
146
|
+
) as sb:
|
|
147
|
+
raw = await sb.read_file_bytes("/data/state.json")
|
|
148
|
+
assert raw.decode() == '{"count": 1}'
|
|
149
|
+
|
|
150
|
+
asyncio.run(main())
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Docker creates the volume automatically if it does not exist yet.
|
|
154
|
+
|
|
115
155
|
## Configuration
|
|
116
156
|
|
|
117
|
-
<!-- name: test_readme_config -->
|
|
157
|
+
<!-- name: test_readme_config; fixtures: docker -->
|
|
118
158
|
```python
|
|
159
|
+
import os
|
|
119
160
|
from axio_tools_docker import DockerSandbox
|
|
120
161
|
|
|
121
162
|
sandbox = DockerSandbox(
|
|
122
|
-
"unix:///var/run/docker.sock",
|
|
163
|
+
os.getenv("DOCKER_HOST", "unix:///var/run/docker.sock"), # Docker daemon URL, optional
|
|
123
164
|
image="python:3.12-slim",
|
|
124
|
-
memory="512m",
|
|
125
|
-
cpus="2.0",
|
|
126
|
-
network=False,
|
|
165
|
+
memory="512m", # memory limit: "256m", "1g", …
|
|
166
|
+
cpus="2.0", # CPU limit
|
|
167
|
+
network=False, # False=none, True=default, str=explicit mode
|
|
127
168
|
workdir="/workspace",
|
|
128
|
-
volumes={"/workspace": "/tmp/host-dir"},
|
|
169
|
+
volumes={"/workspace": "/tmp/host-dir"}, # {container_path: host_path}
|
|
170
|
+
named_volumes={"/data": "my-project-data"}, # named Docker volumes
|
|
171
|
+
volumes_remove=False, # remove named volumes on exit
|
|
129
172
|
env={"PYTHONPATH": "/app"},
|
|
130
173
|
user="nobody",
|
|
131
174
|
name="my-agent-sandbox",
|
|
132
175
|
remove=False,
|
|
133
|
-
read_only=True,
|
|
134
|
-
shm_size="64m",
|
|
135
|
-
cap_add=["NET_ADMIN"],
|
|
136
|
-
cap_drop=["ALL"],
|
|
176
|
+
read_only=True, # read-only root filesystem
|
|
177
|
+
shm_size="64m", # /dev/shm size
|
|
178
|
+
cap_add=["NET_ADMIN"], # add Linux capabilities
|
|
179
|
+
cap_drop=["ALL"], # drop Linux capabilities
|
|
137
180
|
privileged=False,
|
|
138
181
|
ulimits={"nofile": (1024, 65536), "nproc": 512},
|
|
139
182
|
tmpfs={"/tmp": "size=128m,mode=1777"},
|
|
140
|
-
ports={8080: 8080},
|
|
183
|
+
ports={8080: 8080}, # {container_port: host_port}
|
|
141
184
|
platform="linux/amd64",
|
|
142
185
|
extra_hosts={"host.docker.internal": "host-gateway"},
|
|
143
186
|
devices=["/dev/net/tun", "/dev/sda:/dev/xvda:r"],
|
|
144
187
|
dns=["8.8.8.8", "1.1.1.1"],
|
|
145
188
|
)
|
|
189
|
+
|
|
190
|
+
assert sandbox.image == "python:3.12-slim"
|
|
191
|
+
assert sandbox.memory == "512m"
|
|
192
|
+
assert sandbox.cpus == "2.0"
|
|
193
|
+
assert sandbox.network == False
|
|
146
194
|
```
|
|
147
195
|
|
|
148
196
|
| Parameter | Type | Default | Description |
|
|
@@ -154,6 +202,8 @@ sandbox = DockerSandbox(
|
|
|
154
202
|
| `network` | `bool \| str` | `False` | Network mode. `False` → `none`. `True` → Docker default. String → explicit `NetworkMode` (e.g. `"host"`, `"bridge"`, `"my-net"`). |
|
|
155
203
|
| `workdir` | `str` | `"/workspace"` | Working directory inside the container. |
|
|
156
204
|
| `volumes` | `dict[str, str]` | `{}` | Bind mounts as `{container_path: host_path}`. |
|
|
205
|
+
| `named_volumes` | `dict[str, str]` | `{}` | Named Docker volumes as `{container_path: volume_name}`. Created automatically if absent. |
|
|
206
|
+
| `volumes_remove` | `bool` | `False` | Remove named volumes on exit. No effect when attached to an existing container. |
|
|
157
207
|
| `env` | `dict[str, str]` | `{}` | Environment variables passed to all commands. |
|
|
158
208
|
| `user` | `str` | `""` | User to run as (e.g. `"nobody"`, `"1000"`). |
|
|
159
209
|
| `name` | `str` | `""` | Container name. Attaches to existing container if found; creates new one otherwise. |
|
|
@@ -162,7 +212,7 @@ sandbox = DockerSandbox(
|
|
|
162
212
|
| `shm_size` | `str` | `""` | `/dev/shm` size (e.g. `"64m"`). Useful for PyTorch / shared-memory IPC. |
|
|
163
213
|
| `cap_add` | `list[str]` | `[]` | Linux capabilities to add (e.g. `["NET_ADMIN", "SYS_PTRACE"]`). |
|
|
164
214
|
| `cap_drop` | `list[str]` | `[]` | Linux capabilities to drop (e.g. `["ALL"]`). |
|
|
165
|
-
| `privileged` | `bool` | `False` | Extended privileges
|
|
215
|
+
| `privileged` | `bool` | `False` | Extended privileges - full capability set and device access. Use with care. |
|
|
166
216
|
| `ulimits` | `dict[str, int \| tuple[int, int]]` | `{}` | Resource limits. `{"nofile": 1024}` → soft=hard=1024. `{"nofile": (1024, 65536)}` → soft/hard split. |
|
|
167
217
|
| `tmpfs` | `dict[str, str]` | `{}` | Tmpfs mounts as `{path: options}` (e.g. `{"/tmp": "size=128m,mode=1777"}`). |
|
|
168
218
|
| `ports` | `dict[int, int]` | `{}` | Port bindings as `{container_port: host_port}`. Only meaningful when `network != False`. |
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Pytest configuration for axio-tools-docker."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
import aiodocker
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.fixture(scope="session")
|
|
13
|
+
def docker() -> str:
|
|
14
|
+
docker_url = os.getenv("DOCKER_HOST", "unix:///var/run/docker.sock")
|
|
15
|
+
|
|
16
|
+
async def _probe() -> bool:
|
|
17
|
+
# noinspection PyBroadException
|
|
18
|
+
try:
|
|
19
|
+
async with aiodocker.Docker(url=docker_url) as client:
|
|
20
|
+
await client.system.info()
|
|
21
|
+
return True
|
|
22
|
+
except Exception:
|
|
23
|
+
return False
|
|
24
|
+
|
|
25
|
+
if not asyncio.run(_probe()):
|
|
26
|
+
pytest.skip("Skipping due to missing Docker daemon")
|
|
27
|
+
|
|
28
|
+
return docker_url
|