horus-docker 0.0.1__py3-none-any.whl
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.
- horus_docker/__init__.py +6 -0
- horus_docker/executor/__init__.py +6 -0
- horus_docker/executor/docker.py +228 -0
- horus_docker/i18n.py +22 -0
- horus_docker/locale/en/LC_MESSAGES/horus_docker.mo +0 -0
- horus_docker/locale/en/LC_MESSAGES/horus_docker.po +62 -0
- horus_docker/locale/messages.pot +59 -0
- horus_docker/py.typed +0 -0
- horus_docker-0.0.1.dist-info/METADATA +8 -0
- horus_docker-0.0.1.dist-info/RECORD +12 -0
- horus_docker-0.0.1.dist-info/WHEEL +4 -0
- horus_docker-0.0.1.dist-info/entry_points.txt +2 -0
horus_docker/__init__.py
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
#
|
|
2
|
+
# horus_docker
|
|
3
|
+
# Copyright (c) 2026 Temple Compute
|
|
4
|
+
#
|
|
5
|
+
# MIT License
|
|
6
|
+
#
|
|
7
|
+
"""
|
|
8
|
+
Docker executor implementation for Horus.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
import shlex
|
|
13
|
+
from typing import TYPE_CHECKING, ClassVar
|
|
14
|
+
|
|
15
|
+
from horus_builtin.runtime.command import CommandRuntime
|
|
16
|
+
from horus_builtin.runtime.substitution import substitute
|
|
17
|
+
from horus_runtime.core.executor.base import BaseExecutor, RuntimeFilterType
|
|
18
|
+
from horus_runtime.core.task.exceptions import TaskExecutionError
|
|
19
|
+
from horus_runtime.logging import horus_logger
|
|
20
|
+
from pydantic import Field
|
|
21
|
+
|
|
22
|
+
from horus_docker.i18n import tr as _
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from horus_runtime.core.task.base import BaseTask
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class DockerExecutor(BaseExecutor):
|
|
29
|
+
"""
|
|
30
|
+
Runs the task's command inside a Docker container.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
kind: str = "docker_executor"
|
|
34
|
+
kind_name: ClassVar[str] = "Docker Executor"
|
|
35
|
+
kind_description: ClassVar[str] = _(
|
|
36
|
+
"Executes a command inside a Docker container on the task target."
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
runtimes: ClassVar[RuntimeFilterType] = (CommandRuntime,)
|
|
40
|
+
|
|
41
|
+
image: str
|
|
42
|
+
"""
|
|
43
|
+
Docker image tag (e.g. ``python:3.13-slim``). When :attr:`dockerfile` is
|
|
44
|
+
set this becomes the tag assigned to the locally-built image.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
env: dict[str, str] = Field(default_factory=dict)
|
|
48
|
+
"""
|
|
49
|
+
Environment variables to set inside the container, as ``NAME -> value``.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
volumes: dict[str, str] = Field(default_factory=dict)
|
|
53
|
+
"""
|
|
54
|
+
Bind mounts as ``host_path -> container_path`` (read-write).
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
ports: dict[str, str] = Field(default_factory=dict)
|
|
58
|
+
"""
|
|
59
|
+
Port mappings as ``host_port -> container_port``.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
working_dir: str | None = None
|
|
63
|
+
"""
|
|
64
|
+
Working directory inside the container (``-w`` flag).
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
network: str | None = None
|
|
68
|
+
"""
|
|
69
|
+
Docker network to attach the container to.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
entrypoint: str | None = None
|
|
73
|
+
"""
|
|
74
|
+
Override for the image's ``ENTRYPOINT`` (``--entrypoint`` flag).
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
user: str | None = None
|
|
78
|
+
"""
|
|
79
|
+
User (``name``, ``uid``, or ``uid:gid``) to run the command as.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
auto_remove: bool = True
|
|
83
|
+
"""
|
|
84
|
+
Add ``--rm`` so the container is removed when it exits.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
dockerfile: str | None = None
|
|
88
|
+
"""
|
|
89
|
+
Inline Dockerfile content. Supports ``$id`` / ``${task.attr}``
|
|
90
|
+
substitution for artifacts and task attributes.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
build_context: str | None = None
|
|
94
|
+
"""
|
|
95
|
+
Build context path on the target for ``docker build``. Defaults to
|
|
96
|
+
``task.working_dir`` when :attr:`dockerfile` is set.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
def _docker_run_cmd(self, prepared_command: str) -> str:
|
|
100
|
+
"""Return the full ``docker run`` CLI command string."""
|
|
101
|
+
parts = ["docker", "run"]
|
|
102
|
+
if self.auto_remove:
|
|
103
|
+
parts.append("--rm")
|
|
104
|
+
for k, v in self.env.items():
|
|
105
|
+
parts += ["-e", shlex.quote(f"{k}={v}")]
|
|
106
|
+
for host, container in self.volumes.items():
|
|
107
|
+
parts += ["-v", shlex.quote(f"{host}:{container}")]
|
|
108
|
+
for host_p, cont_p in self.ports.items():
|
|
109
|
+
parts += ["-p", shlex.quote(f"{host_p}:{cont_p}")]
|
|
110
|
+
if self.working_dir:
|
|
111
|
+
parts += ["-w", shlex.quote(self.working_dir)]
|
|
112
|
+
if self.network:
|
|
113
|
+
parts += ["--network", shlex.quote(self.network)]
|
|
114
|
+
if self.entrypoint is not None:
|
|
115
|
+
parts += ["--entrypoint", shlex.quote(self.entrypoint)]
|
|
116
|
+
if self.user:
|
|
117
|
+
parts += ["-u", shlex.quote(self.user)]
|
|
118
|
+
parts += [
|
|
119
|
+
shlex.quote(self.image),
|
|
120
|
+
"/bin/sh",
|
|
121
|
+
"-c",
|
|
122
|
+
shlex.quote(prepared_command),
|
|
123
|
+
]
|
|
124
|
+
return " ".join(parts)
|
|
125
|
+
|
|
126
|
+
async def _build_image(self, task: "BaseTask") -> None:
|
|
127
|
+
"""
|
|
128
|
+
Render :attr:`dockerfile`, upload it to the target, and build the
|
|
129
|
+
image tagged as :attr:`image`.
|
|
130
|
+
"""
|
|
131
|
+
rendered = substitute(self.dockerfile or "", task)
|
|
132
|
+
build_dir = f"{task.working_dir}/.horus_docker"
|
|
133
|
+
dockerfile_path = f"{build_dir}/Dockerfile"
|
|
134
|
+
|
|
135
|
+
await task.target.mkdir(build_dir)
|
|
136
|
+
await task.target.put_file(rendered.encode(), dockerfile_path)
|
|
137
|
+
|
|
138
|
+
context = self.build_context or task.working_dir
|
|
139
|
+
build_cmd = (
|
|
140
|
+
f"docker build"
|
|
141
|
+
f" -t {shlex.quote(self.image)}"
|
|
142
|
+
f" -f {shlex.quote(dockerfile_path)}"
|
|
143
|
+
f" {shlex.quote(str(context))}"
|
|
144
|
+
)
|
|
145
|
+
proc = await task.target.run_command(build_cmd)
|
|
146
|
+
stdout, stderr = await proc.communicate()
|
|
147
|
+
out = stdout.decode(errors="replace").strip() if stdout else ""
|
|
148
|
+
err = stderr.decode(errors="replace").strip() if stderr else ""
|
|
149
|
+
if out:
|
|
150
|
+
horus_logger.log.debug(out)
|
|
151
|
+
if err:
|
|
152
|
+
horus_logger.log.debug(err)
|
|
153
|
+
if proc.returncode != 0:
|
|
154
|
+
raise TaskExecutionError(
|
|
155
|
+
_("docker build failed with exit code %(code)s")
|
|
156
|
+
% {"code": proc.returncode}
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
async def _execute(self, task: "BaseTask") -> None:
|
|
160
|
+
"""
|
|
161
|
+
Build the image (if :attr:`dockerfile` is set), run the task's command
|
|
162
|
+
inside a container on the target, then clean up the image.
|
|
163
|
+
"""
|
|
164
|
+
if not isinstance(task.runtime, CommandRuntime):
|
|
165
|
+
raise TaskExecutionError(
|
|
166
|
+
_("DockerExecutor only supports CommandRuntime runtimes.")
|
|
167
|
+
)
|
|
168
|
+
prepared_command = await task.runtime.setup_runtime(task)
|
|
169
|
+
|
|
170
|
+
if self.dockerfile:
|
|
171
|
+
await self._build_image(task)
|
|
172
|
+
|
|
173
|
+
run_cmd = self._docker_run_cmd(prepared_command)
|
|
174
|
+
horus_logger.log.debug(
|
|
175
|
+
_(
|
|
176
|
+
"Running task %(task_id)s in Docker image %(image)s: "
|
|
177
|
+
"%(command)s"
|
|
178
|
+
)
|
|
179
|
+
% {
|
|
180
|
+
"task_id": task.id,
|
|
181
|
+
"image": self.image,
|
|
182
|
+
"command": prepared_command,
|
|
183
|
+
}
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
proc = await task.target.run_command(run_cmd, cwd=task.working_dir)
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
stdout, stderr = await proc.communicate()
|
|
190
|
+
except asyncio.CancelledError:
|
|
191
|
+
proc.kill()
|
|
192
|
+
await proc.wait()
|
|
193
|
+
raise
|
|
194
|
+
|
|
195
|
+
out = stdout.decode(errors="replace").strip() if stdout else ""
|
|
196
|
+
err = stderr.decode(errors="replace").strip() if stderr else ""
|
|
197
|
+
if out:
|
|
198
|
+
horus_logger.log.info(out)
|
|
199
|
+
if err:
|
|
200
|
+
horus_logger.log.warning(err)
|
|
201
|
+
|
|
202
|
+
try:
|
|
203
|
+
if proc.returncode != 0:
|
|
204
|
+
horus_logger.log.error(
|
|
205
|
+
_(
|
|
206
|
+
"Container for task %(task_id)s exited with code "
|
|
207
|
+
"%(code)s. Output: %(out)s"
|
|
208
|
+
)
|
|
209
|
+
% {
|
|
210
|
+
"task_id": task.id,
|
|
211
|
+
"code": proc.returncode,
|
|
212
|
+
"out": (out or err).strip(),
|
|
213
|
+
}
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
raise TaskExecutionError(
|
|
217
|
+
_("Container exited with code %(code)s")
|
|
218
|
+
% {"code": proc.returncode}
|
|
219
|
+
)
|
|
220
|
+
finally:
|
|
221
|
+
if self.dockerfile:
|
|
222
|
+
try:
|
|
223
|
+
rmi = await task.target.run_command(
|
|
224
|
+
f"docker rmi -f {shlex.quote(self.image)}"
|
|
225
|
+
)
|
|
226
|
+
await rmi.wait()
|
|
227
|
+
except Exception:
|
|
228
|
+
pass
|
horus_docker/i18n.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#
|
|
2
|
+
# horus_docker
|
|
3
|
+
# Copyright (c) 2026 Temple Compute
|
|
4
|
+
#
|
|
5
|
+
# MIT License
|
|
6
|
+
#
|
|
7
|
+
"""
|
|
8
|
+
Localization for horus_docker.
|
|
9
|
+
|
|
10
|
+
Import ``tr`` (aliased as ``_``) in any module that has user-visible strings::
|
|
11
|
+
|
|
12
|
+
from horus_docker.i18n import tr as _
|
|
13
|
+
|
|
14
|
+
_("Something happened.")
|
|
15
|
+
_("%(n)s item processed", "%(n)s items processed", n=count)
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from horus_runtime.i18n import make_translator
|
|
21
|
+
|
|
22
|
+
tr = make_translator("horus_docker", Path(__file__).parent / "locale")
|
|
Binary file
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# English translations for horus_docker.
|
|
2
|
+
# Copyright (C) 2026 Temple Compute
|
|
3
|
+
# This file is distributed under the same license as the horus_docker
|
|
4
|
+
# project.
|
|
5
|
+
# Temple Compute, 2026.
|
|
6
|
+
#
|
|
7
|
+
msgid ""
|
|
8
|
+
msgstr ""
|
|
9
|
+
"Project-Id-Version: horus_docker VERSION\n"
|
|
10
|
+
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
|
11
|
+
"POT-Creation-Date: 2026-05-31 00:04+0200\n"
|
|
12
|
+
"PO-Revision-Date: 2026-05-31 00:07+0200\n"
|
|
13
|
+
"Last-Translator: Temple Compute\n"
|
|
14
|
+
"Language: en\n"
|
|
15
|
+
"Language-Team: en <LL@li.org>\n"
|
|
16
|
+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
17
|
+
"MIME-Version: 1.0\n"
|
|
18
|
+
"Content-Type: text/plain; charset=utf-8\n"
|
|
19
|
+
"Content-Transfer-Encoding: 8bit\n"
|
|
20
|
+
"Generated-By: Babel 2.18.0\n"
|
|
21
|
+
|
|
22
|
+
#: src/horus_docker/executor/docker.py:41
|
|
23
|
+
msgid "Executes a command inside a Docker container."
|
|
24
|
+
msgstr "Executes a command inside a Docker container."
|
|
25
|
+
|
|
26
|
+
#: src/horus_docker/executor/docker.py:148
|
|
27
|
+
msgid "DockerExecutor only supports CommandRuntime runtimes."
|
|
28
|
+
msgstr "DockerExecutor only supports CommandRuntime runtimes."
|
|
29
|
+
|
|
30
|
+
#: src/horus_docker/executor/docker.py:155
|
|
31
|
+
#, python-format
|
|
32
|
+
msgid "Running task %(task_id)s in Docker image %(image)s: %(command)s"
|
|
33
|
+
msgstr "Running task %(task_id)s in Docker image %(image)s: %(command)s"
|
|
34
|
+
|
|
35
|
+
#: src/horus_docker/executor/docker.py:172
|
|
36
|
+
#, python-format
|
|
37
|
+
msgid ""
|
|
38
|
+
"Logs from task %(task_id)s Docker container:\n"
|
|
39
|
+
"%(logs)s"
|
|
40
|
+
msgstr ""
|
|
41
|
+
"Logs from task %(task_id)s Docker container:\n"
|
|
42
|
+
"%(logs)s"
|
|
43
|
+
|
|
44
|
+
#: src/horus_docker/executor/docker.py:178
|
|
45
|
+
#, python-format
|
|
46
|
+
msgid "Docker execution failed for task %(task_id)s: %(error)s"
|
|
47
|
+
msgstr "Docker execution failed for task %(task_id)s: %(error)s"
|
|
48
|
+
|
|
49
|
+
#: src/horus_docker/executor/docker.py:182
|
|
50
|
+
#, python-format
|
|
51
|
+
msgid "Docker execution failed: %(error)s"
|
|
52
|
+
msgstr "Docker execution failed: %(error)s"
|
|
53
|
+
|
|
54
|
+
#: src/horus_docker/executor/docker.py:188
|
|
55
|
+
#, python-format
|
|
56
|
+
msgid "Container for task %(task_id)s exited with code %(code)s. Logs: %(logs)s"
|
|
57
|
+
msgstr "Container for task %(task_id)s exited with code %(code)s. Logs: %(logs)s"
|
|
58
|
+
|
|
59
|
+
#: src/horus_docker/executor/docker.py:198
|
|
60
|
+
#, python-format
|
|
61
|
+
msgid "Container exited with code %(code)s"
|
|
62
|
+
msgstr "Container exited with code %(code)s"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Translations template for horus_docker.
|
|
2
|
+
# Copyright (C) 2026 Temple Compute
|
|
3
|
+
# This file is distributed under the same license as the horus_docker
|
|
4
|
+
# project.
|
|
5
|
+
# FIRST AUTHOR <EMAIL@ADDRESS>, 2026.
|
|
6
|
+
#
|
|
7
|
+
#, fuzzy
|
|
8
|
+
msgid ""
|
|
9
|
+
msgstr ""
|
|
10
|
+
"Project-Id-Version: horus_docker VERSION\n"
|
|
11
|
+
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
|
12
|
+
"POT-Creation-Date: 2026-05-31 00:04+0200\n"
|
|
13
|
+
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
|
14
|
+
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
15
|
+
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
16
|
+
"MIME-Version: 1.0\n"
|
|
17
|
+
"Content-Type: text/plain; charset=utf-8\n"
|
|
18
|
+
"Content-Transfer-Encoding: 8bit\n"
|
|
19
|
+
"Generated-By: Babel 2.18.0\n"
|
|
20
|
+
|
|
21
|
+
#: src/horus_docker/executor/docker.py:41
|
|
22
|
+
msgid "Executes a command inside a Docker container."
|
|
23
|
+
msgstr ""
|
|
24
|
+
|
|
25
|
+
#: src/horus_docker/executor/docker.py:148
|
|
26
|
+
msgid "DockerExecutor only supports CommandRuntime runtimes."
|
|
27
|
+
msgstr ""
|
|
28
|
+
|
|
29
|
+
#: src/horus_docker/executor/docker.py:155
|
|
30
|
+
#, python-format
|
|
31
|
+
msgid "Running task %(task_id)s in Docker image %(image)s: %(command)s"
|
|
32
|
+
msgstr ""
|
|
33
|
+
|
|
34
|
+
#: src/horus_docker/executor/docker.py:172
|
|
35
|
+
#, python-format
|
|
36
|
+
msgid ""
|
|
37
|
+
"Logs from task %(task_id)s Docker container:\n"
|
|
38
|
+
"%(logs)s"
|
|
39
|
+
msgstr ""
|
|
40
|
+
|
|
41
|
+
#: src/horus_docker/executor/docker.py:178
|
|
42
|
+
#, python-format
|
|
43
|
+
msgid "Docker execution failed for task %(task_id)s: %(error)s"
|
|
44
|
+
msgstr ""
|
|
45
|
+
|
|
46
|
+
#: src/horus_docker/executor/docker.py:182
|
|
47
|
+
#, python-format
|
|
48
|
+
msgid "Docker execution failed: %(error)s"
|
|
49
|
+
msgstr ""
|
|
50
|
+
|
|
51
|
+
#: src/horus_docker/executor/docker.py:188
|
|
52
|
+
#, python-format
|
|
53
|
+
msgid "Container for task %(task_id)s exited with code %(code)s. Logs: %(logs)s"
|
|
54
|
+
msgstr ""
|
|
55
|
+
|
|
56
|
+
#: src/horus_docker/executor/docker.py:198
|
|
57
|
+
#, python-format
|
|
58
|
+
msgid "Container exited with code %(code)s"
|
|
59
|
+
msgstr ""
|
horus_docker/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: horus_docker
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A custom plugin for the Horus runtime, providing additional functionality and integrations.
|
|
5
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Requires-Python: >=3.13
|
|
8
|
+
Requires-Dist: horus-runtime>=0.1.3
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
horus_docker/__init__.py,sha256=jqNo4q-TC_OSD0XfvOsEnKgSy246GNyctGK6a95Dtq8,71
|
|
2
|
+
horus_docker/i18n.py,sha256=VkI5_7OcLDkcFNn7iVB0QiWiMyDLNekWFsvke2WCkRQ,473
|
|
3
|
+
horus_docker/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
horus_docker/executor/__init__.py,sha256=jqNo4q-TC_OSD0XfvOsEnKgSy246GNyctGK6a95Dtq8,71
|
|
5
|
+
horus_docker/executor/docker.py,sha256=0_Uk8ej6_koltqcwTGqpxgcxxSIZ7XX4ONWduyqXjYI,7275
|
|
6
|
+
horus_docker/locale/messages.pot,sha256=Eb0ZmjnemeiRfcQaB1W1QgPvXWH4bvFED6EE7EZYiD8,1643
|
|
7
|
+
horus_docker/locale/en/LC_MESSAGES/horus_docker.mo,sha256=ZBUGlJBbxIudnS77Gu5S7FpYGFn4fKzy2Wcxv1B8lao,1403
|
|
8
|
+
horus_docker/locale/en/LC_MESSAGES/horus_docker.po,sha256=hb21SXf_ZRBEvwF_vpaJiojFZUlTh6I3oX9ISbYfNYo,2083
|
|
9
|
+
horus_docker-0.0.1.dist-info/METADATA,sha256=uLjRib1kIia2HQM4X7QBAx5RTr4MbUSQ0Dn54h2i4QA,316
|
|
10
|
+
horus_docker-0.0.1.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
11
|
+
horus_docker-0.0.1.dist-info/entry_points.txt,sha256=fHxWnpDnTT9IgWhWIZ0y_emEst9KTKYMmFBtgClXmXY,55
|
|
12
|
+
horus_docker-0.0.1.dist-info/RECORD,,
|