Skip to content

Commit 78cc27a

Browse files
authored
Update docker.py to support podman (OpenHands#6778)
1 parent 2db7a50 commit 78cc27a

3 files changed

Lines changed: 33 additions & 11 deletions

File tree

openhands/runtime/builder/docker.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,25 @@ def __init__(self, docker_client: docker.DockerClient):
1919

2020
version_info = self.docker_client.version()
2121
server_version = version_info.get('Version', '').replace('-', '.')
22-
if tuple(map(int, server_version.split('.')[:2])) < (18, 9):
22+
self.is_podman = version_info.get('Components')[0].get('Name').startswith('Podman')
23+
if tuple(map(int, server_version.split('.')[:2])) < (18, 9) and not self.is_podman:
2324
raise AgentRuntimeBuildError(
2425
'Docker server version must be >= 18.09 to use BuildKit'
2526
)
2627

28+
if self.is_podman and tuple(map(int, server_version.split('.')[:2])) < (4, 9):
29+
raise AgentRuntimeBuildError(
30+
'Podman server version must be >= 4.9.0'
31+
)
32+
2733
self.rolling_logger = RollingLogger(max_lines=10)
2834

2935
@staticmethod
30-
def check_buildx():
36+
def check_buildx(is_podman: bool = False):
3137
"""Check if Docker Buildx is available"""
3238
try:
3339
result = subprocess.run(
34-
['docker', 'buildx', 'version'], capture_output=True, text=True
40+
['docker' if not is_podman else 'podman', 'buildx', 'version'], capture_output=True, text=True
3541
)
3642
return result.returncode == 0
3743
except FileNotFoundError:
@@ -68,12 +74,18 @@ def build(
6874
self.docker_client = docker.from_env()
6975
version_info = self.docker_client.version()
7076
server_version = version_info.get('Version', '').split('+')[0].replace('-', '.')
71-
if tuple(map(int, server_version.split('.'))) < (18, 9):
77+
self.is_podman = version_info.get('Components')[0].get('Name').startswith('Podman')
78+
if tuple(map(int, server_version.split('.'))) < (18, 9) and not self.is_podman:
7279
raise AgentRuntimeBuildError(
7380
'Docker server version must be >= 18.09 to use BuildKit'
7481
)
7582

76-
if not DockerRuntimeBuilder.check_buildx():
83+
if self.is_podman and tuple(map(int, server_version.split('.'))) < (4, 9):
84+
raise AgentRuntimeBuildError(
85+
'Podman server version must be >= 4.9.0'
86+
)
87+
88+
if not DockerRuntimeBuilder.check_buildx(self.is_podman):
7789
# when running openhands in a container, there might not be a "docker"
7890
# binary available, in which case we need to download docker binary.
7991
# since the official openhands app image is built from debian, we use
@@ -110,7 +122,7 @@ def build(
110122
target_image_tag = tags[1].split(':')[1] if len(tags) > 1 else None
111123

112124
buildx_cmd = [
113-
'docker',
125+
'docker' if not self.is_podman else 'podman',
114126
'buildx',
115127
'build',
116128
'--progress=plain',
@@ -139,7 +151,7 @@ def build(
139151
buildx_cmd.append(path) # must be last!
140152

141153
self.rolling_logger.start(
142-
'================ DOCKER BUILD STARTED ================'
154+
f'================ {buildx_cmd[0].upper()} BUILD STARTED ================'
143155
)
144156

145157
try:

tests/unit/test_docker_runtime.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ def mock_docker_client():
2121
mock_client.return_value.containers.get.return_value = container_mock
2222
mock_client.return_value.containers.run.return_value = container_mock
2323
# Mock version info for BuildKit check
24-
mock_client.return_value.version.return_value = {'Version': '20.10.0'}
24+
mock_client.return_value.version.return_value = {
25+
'Version': '20.10.0',
26+
'Components': [{'Name': 'Engine', 'Version': '20.10.0'}],
27+
} # Ensure version is >= 18.09
2528
yield mock_client.return_value
2629

2730

tests/unit/test_runtime_build.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ def temp_dir(tmp_path_factory: TempPathFactory) -> str:
4040
def mock_docker_client():
4141
mock_client = MagicMock(spec=docker.DockerClient)
4242
mock_client.version.return_value = {
43-
'Version': '19.03'
43+
'Version': '20.10.0',
44+
'Components': [{'Name': 'Engine', 'Version': '20.10.0'}],
4445
} # Ensure version is >= 18.09
4546
return mock_client
4647

@@ -612,15 +613,21 @@ def test_build_image_from_repo(docker_runtime_builder, tmp_path):
612613

613614
def test_image_exists_local(docker_runtime_builder):
614615
mock_client = MagicMock()
615-
mock_client.version().get.return_value = '18.9'
616+
mock_client.version.return_value = {
617+
'Version': '20.10.0',
618+
'Components': [{'Name': 'Engine', 'Version': '20.10.0'}],
619+
} # Ensure version is >= 18.09
616620
builder = DockerRuntimeBuilder(mock_client)
617621
image_name = 'existing-local:image' # The mock pretends this exists by default
618622
assert builder.image_exists(image_name)
619623

620624

621625
def test_image_exists_not_found():
622626
mock_client = MagicMock()
623-
mock_client.version().get.return_value = '18.9'
627+
mock_client.version.return_value = {
628+
'Version': '20.10.0',
629+
'Components': [{'Name': 'Engine', 'Version': '20.10.0'}],
630+
} # Ensure version is >= 18.09
624631
mock_client.images.get.side_effect = docker.errors.ImageNotFound(
625632
"He doesn't like you!"
626633
)

0 commit comments

Comments
 (0)